X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/2e282f9210b79dde95e534769efaae078c1b8db1..a6d583c00a8079c98000fdc80c870f7cdcc9f291:/lisp/textmodes/bibtex.el diff --git a/lisp/textmodes/bibtex.el b/lisp/textmodes/bibtex.el index cf888c49a3..c82f2dcf3d 100644 --- a/lisp/textmodes/bibtex.el +++ b/lisp/textmodes/bibtex.el @@ -1,13 +1,15 @@ ;;; bibtex.el --- BibTeX mode for GNU Emacs -;; Copyright (C) 1992, 1994, 1995 Free Software Foundation, Inc. - -;; Author: Stefan Schoef -;; Bengt Martensson -;; Mark Shapiro -;; Mike Newton -;; Aaron Larson -;; Maintainer: Stefan Schoef +;; Copyright (C) 1992, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, +;; 2004, 2005, 2006 Free Software Foundation, Inc. + +;; Author: Stefan Schoef +;; Bengt Martensson +;; Mark Shapiro +;; Mike Newton +;; Aaron Larson +;; Dirk Herrmann +;; Maintainer: Roland Winkler ;; Keywords: BibTeX, LaTeX, TeX ;; This file is part of GNU Emacs. @@ -23,445 +25,916 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to -;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: + ;; Major mode for editing and validating BibTeX files. ;; Usage: -;; See documentation for function bibtex-mode (or type "\M-x describe-mode" -;; when you are in bibtex-mode). +;; See documentation for function bibtex-mode or type "\M-x describe-mode" +;; when you are in BibTeX mode. ;; Todo: ;; Distribute texinfo file. -;; Known Bugs: -;; 1. using regular expressions to match the entire BibTeX entry dies -;; on long entries (e.g. those containing abstracts) since -;; the length of regular expression matches is fairly limited. -;; 2. Calling bibtex-find-text in a string entry results in the -;; error message "Can't find enclosing Bibtex field" instead of -;; moving to the empty string. [reported by gernot@cs.unsw.oz.au] -;; 3. Quotes inside quote-parenthesized fields (like -;; `author = "Stefan Sch{\"o}f"') break bibtex-validate-buffer. -;; Further, you must use braces here, if you want to set -;; bibtex-maintain-sorted-entries to a non-nil value. - -;; (current keeper: schoef@informatik.uni-oldenburg.de -;; previous: alarson@src.honeywell.com) - ;;; Code: -;; User Options: - -(defvar bibtex-field-left-delimiter "{" - "*Set this to { or \" according to your personal preferences. -This variable is buffer local.") -(make-variable-buffer-local 'bibtex-field-left-delimiter) - -(defvar bibtex-field-right-delimiter "}" - "*Set this to } or \" according to your personal preferences. -This variable is buffer local.") -(make-variable-buffer-local 'bibtex-field-right-delimiter) - -(defvar bibtex-include-OPTcrossref '("InProceedings" "InCollection") - "*All entries listed here will have an OPTcrossref field.") - -(defvar bibtex-include-OPTkey t - "*If non-nil, all entries will have an OPTkey field.") - -(defvar bibtex-include-OPTannote t - "*If non-nil, all entries will have an OPTannote field.") -(defvar bibtex-mode-user-optional-fields nil - "*List of optional fields the user wants to have always present. -Entries should be lists of strings with two elements (first element = -name of the field, second element = comment to appear in the echo area).") +(require 'button) -(defvar bibtex-clean-entry-zap-empty-opts t - "*If non-nil, bibtex-clean-entry will delete all empty optional fields.") + +;; User Options: -(defvar bibtex-sort-ignore-string-entries t - "*If non-nil, BibTeX @STRING entries are not sort-significant. +(defgroup bibtex nil + "BibTeX mode." + :group 'tex + :prefix "bibtex-") + +(defgroup bibtex-autokey nil + "Generate automatically a key from the author/editor and the title field." + :group 'bibtex + :prefix "bibtex-autokey-") + +(defcustom bibtex-mode-hook nil + "List of functions to call on entry to BibTeX mode." + :group 'bibtex + :type 'hook) + +(defcustom bibtex-field-delimiters 'braces + "Type of field delimiters. Allowed values are `braces' or `double-quotes'." + :group 'bibtex + :type '(choice (const braces) + (const double-quotes))) + +(defcustom bibtex-entry-delimiters 'braces + "Type of entry delimiters. Allowed values are `braces' or `parentheses'." + :group 'bibtex + :type '(choice (const braces) + (const parentheses))) + +(defcustom bibtex-include-OPTcrossref '("InProceedings" "InCollection") + "List of BibTeX entries that get an OPTcrossref field." + :group 'bibtex + :type '(repeat string)) + +(defcustom bibtex-include-OPTkey t + "If non-nil, all newly created entries get an OPTkey field. +If this is a string, use it as the initial field text. +If this is a function, call it to generate the initial field text." + :group 'bibtex + :type '(choice (const :tag "None" nil) + (string :tag "Initial text") + (function :tag "Initialize Function" :value fun) + (const :tag "Default" t))) +(put 'bibtex-include-OPTkey 'risky-local-variable t) + +(defcustom bibtex-user-optional-fields + '(("annote" "Personal annotation (ignored)")) + "List of optional fields the user wants to have always present. +Entries should be of the same form as the OPTIONAL and +CROSSREF-OPTIONAL lists in `bibtex-entry-field-alist' (which see)." + :group 'bibtex + :type '(repeat (group (string :tag "Field") + (string :tag "Comment") + (option (group :inline t + :extra-offset -4 + (choice :tag "Init" :value "" + string + function)))))) +(put 'bibtex-user-optional-fields 'risky-local-variable t) + +(defcustom bibtex-entry-format + '(opts-or-alts required-fields numerical-fields) + "Type of formatting performed by `bibtex-clean-entry'. +It may be t, nil, or a list of symbols out of the following: +opts-or-alts Delete empty optional and alternative fields and + remove OPT and ALT prefixes from used fields. +required-fields Signal an error if a required field is missing. +numerical-fields Delete delimiters around numeral fields. +page-dashes Change double dashes in page field to single dash + (for scribe compatibility). +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. +realign Realign entries, so that field texts and perhaps equal + signs (depending on the value of + `bibtex-align-at-equal-sign') begin in the same column. +last-comma Add or delete comma on end of last field in entry, + according to value of `bibtex-comma-after-last-field'. +delimiters Change delimiters according to variables + `bibtex-field-delimiters' and `bibtex-entry-delimiters'. +unify-case Change case of entry and field names. + +The value t means do all of the above formatting actions. +The value nil means do no formatting at all." + :group 'bibtex + :type '(choice (const :tag "None" nil) + (const :tag "All" t) + (set :menu-tag "Some" + (const opts-or-alts) + (const required-fields) + (const numerical-fields) + (const page-dashes) + (const inherit-booktitle) + (const realign) + (const last-comma) + (const delimiters) + (const unify-case)))) + +(defcustom bibtex-clean-entry-hook nil + "List of functions to call when entry has been cleaned. +Functions are called with point inside the cleaned entry, and the buffer +narrowed to just the entry." + :group 'bibtex + :type 'hook) + +(defcustom bibtex-maintain-sorted-entries nil + "If non-nil, BibTeX mode maintains all entries in sorted order. +Allowed non-nil values are: +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. +entry-class The entries are divided into classes according to their + entry name, see `bibtex-sort-entry-class'. Within each class + the entries are sorted alphabetically. +See also `bibtex-sort-ignore-string-entries'." + :group 'bibtex + :type '(choice (const nil) + (const plain) + (const crossref) + (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") + (catch-all) + ("Book" "Proceedings")) + "List of classes of BibTeX entry names, used for sorting entries. +If value of `bibtex-maintain-sorted-entries' is `entry-class' +entries are ordered according to the classes they belong to. Each +class contains a list of entry names. An entry `catch-all' applies +to all entries not explicitly mentioned." + :group 'BibTeX + :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. That means they are ignored when determining ordering of the buffer -(e.g. sorting, locating alphabetical position for new entries, etc.). -This variable is buffer local.") -(make-variable-buffer-local 'bibtex-sort-ignore-string-entries) - -(defvar bibtex-maintain-sorted-entries nil - "*If non-nil, bibtex-mode maintains all BibTeX entries in sorted order. -Setting this variable to nil will strip off some comfort (e.g. TAB -completion for reference keys in minibuffer, automatic detection of -duplicates) from bibtex-mode. See also bibtex-sort-ignore-string-entries. -This variable is buffer local.") -(make-variable-buffer-local 'bibtex-maintain-sorted-entries) +\(e.g., sorting, locating alphabetical position for new entries, etc.)." + :group 'bibtex + :type 'boolean) + +(defcustom bibtex-field-kill-ring-max 20 + "Max length of `bibtex-field-kill-ring' before discarding oldest elements." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-entry-kill-ring-max 20 + "Max length of `bibtex-entry-kill-ring' before discarding oldest elements." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-parse-keys-timeout 60 + "Time interval in seconds for parsing BibTeX buffers during idle time. +Parsing initializes `bibtex-reference-keys' and `bibtex-strings'." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-parse-keys-fast t + "If non-nil, use fast but simplified algorithm for parsing BibTeX keys. +If parsing fails, try to set this variable to nil." + :group 'bibtex + :type 'boolean) (defvar bibtex-entry-field-alist - '( - ("Article" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the article (BibTeX converts it to lowercase)") - ("journal" "Name of the journal (use string, remove braces)") - ("year" "Year of publication")) - (("volume" "Volume of the journal") - ("number" "Number of the journal") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the journal") - ("note" "Remarks to be put at the end of the \\bibitem"))) - ((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the article (BibTeX converts it to lowercase)")) - (("journal" "Name of the journal (use string, remove braces)") - ("year" "Year of publication") - ("volume" "Volume of the journal") - ("number" "Number of the journal") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the journal") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("Book" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the book") - ("publisher" "Publishing company") - ("year" "Year of publication")) - (("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the book in the series") - ("number" "Number of the book in a small series (overwritten by volume)") - ("series" "Series in which the book appeared") - ("address" "Address of the publisher") - ("edition" "Edition of the book as a capitalized English word") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("Booklet" . (((("title" "Title of the booklet (BibTeX converts it to lowercase)")) - (("author" "Author1 [and Author2 ...] [and others]") - ("howpublished" "The way in which the booklet was published") - ("address" "Address of the publisher") - ("year" "Year of publication") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("InBook" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the book") - ("chapter" "Chapter in the book") - ("publisher" "Publishing company") - ("year" "Year of publication")) - (("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the book in the series") - ("number" "Number of the book in a small series (overwritten by volume)") - ("series" "Series in which the book appeared") - ("address" "Address of the publisher") - ("edition" "Edition of the book as a capitalized English word") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the book") - ("type" "Word to use instead of \"chapter\"") - ("note" "Remarks to be put at the end of the \\bibitem"))) - ((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the book") - ("chapter" "Chapter in the book")) - (("publisher" "Publishing company") - ("year" "Year of publication") - ("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the book in the series") - ("number" "Number of the book in a small series (overwritten by volume)") - ("series" "Series in which the book appeared") - ("address" "Address of the publisher") - ("edition" "Edition of the book as a capitalized English word") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the book") - ("type" "Word to use instead of \"chapter\"") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("InCollection" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the article in book (BibTeX converts it to lowercase)") - ("booktitle" "Name of the book") - ("publisher" "Publishing company") - ("year" "Year of publication")) - (("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the book in the series") - ("number" "Number of the book in a small series (overwritten by volume)") - ("series" "Series in which the book appeared") - ("chapter" "Chapter in the book") - ("type" "Word to use instead of \"chapter\"") - ("address" "Address of the publisher") - ("edition" "Edition of the book as a capitalized English word") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the book") - ("note" "Remarks to be put at the end of the \\bibitem"))) - ((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the article in book (BibTeX converts it to lowercase)") - ("booktitle" "Name of the book")) - (("publisher" "Publishing company") - ("year" "Year of publication") - ("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the book in the series") - ("number" "Number of the book in a small series (overwritten by volume)") - ("series" "Series in which the book appeared") - ("chapter" "Chapter in the book") - ("type" "Word to use instead of \"chapter\"") - ("address" "Address of the publisher") - ("edition" "Edition of the book as a capitalized English word") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the book") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("InProceedings" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the article in proceedings (BibTeX converts it to lowercase)") - ("booktitle" "Name of the conference proceedings") - ("year" "Year of publication")) - (("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the conference proceedings in the series") - ("number" "Number of the conference proceedings in a small series (overwritten by volume)") - ("series" "Series in which the conference proceedings appeared") - ("organization" "Sponsoring organization of the conference") - ("publisher" "Publishing company, its location") - ("address" "Location of the Proceedings") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the conference proceedings") - ("note" "Remarks to be put at the end of the \\bibitem"))) - ((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the article in proceedings (BibTeX converts it to lowercase)") - ("booktitle" "Name of the conference proceedings")) - (("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the conference proceedings in the series") - ("number" "Number of the conference proceedings in a small series (overwritten by volume)") - ("series" "Series in which the conference proceedings appeared") - ("year" "Year of publication") - ("organization" "Sponsoring organization of the conference") - ("publisher" "Publishing company, its location") - ("address" "Location of the Proceedings") - ("month" "Month of the publication as a string (remove braces)") - ("pages" "Pages in the conference proceedings") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("Manual" . (((("title" "Title of the manual")) - (("author" "Author1 [and Author2 ...] [and others]") - ("organization" "Publishing organization of the manual") - ("address" "Address of the organization") - ("edition" "Edition of the manual as a capitalized English word") - ("year" "Year of publication") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - - ("MastersThesis" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the master\'s thesis (BibTeX converts it to lowercase)") - ("school" "School where the master\'s thesis was written") - ("year" "Year of publication")) - (("address" "Address of the school (if not part of field \"school\") or country") - ("type" "Type of the master\'s thesis") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("Misc" . ((() - (("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the reference (BibTeX converts it to lowercase)") - ("howpublished" "The way in which the reference was published") - ("year" "Year of publication") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("PhdThesis" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the PhD. thesis") - ("school" "School where the PhD. thesis was written") - ("year" "Year of publication")) - (("address" "Address of the school (if not part of field \"school\") or country") - ("type" "Type of the PhD. thesis") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("Proceedings" . (((("title" "Title of the conference proceedings") - ("year" "Year of publication")) - (("editor" "Editor1 [and Editor2 ...] [and others]") - ("volume" "Volume of the conference proceedings in the series") - ("number" "Number of the conference proceedings in a small series (overwritten by volume)") - ("series" "Series in which the conference proceedings appeared") - ("publisher" "Publishing company, its location") - ("organization" "Sponsoring organization of the conference") - ("address" "Location of the Proceedings") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("TechReport" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the technical report (BibTeX converts it to lowercase)") - ("institution" "Sponsoring institution of the report") - ("year" "Year of publication")) - (("type" "Type of the report (if other than \"technical report\")") - ("number" "Number of the technical report") - ("address" "Address of the institution (if not part of field \"institution\") or country") - ("month" "Month of the publication as a string (remove braces)") - ("note" "Remarks to be put at the end of the \\bibitem"))))) - ("Unpublished" . (((("author" "Author1 [and Author2 ...] [and others]") - ("title" "Title of the unpublished reference (BibTeX converts it to lowercase)") - ("note" "Remarks to be put at the end of the \\bibitem")) - (("year" "Year of publication") - ("month" "Month of the publication as a string (remove braces)"))))) - ) - - "Defines reference types and their associated fields. -List of -(entry-name (required optional) (crossref-required crossref-optional)) -triples. -If the third element is nil, the first pair is always to be used. -If not, the second pair is to be used in the case of presence of a -crossref field and the third in the case of absence. -Required , optional, crossref-required and crossref-optional are lists. -Each element of these lists is a list of strings with two elements -(first element = name of the field, - second element = comment to appear in the echo area).") - -(defvar bibtex-predefined-strings - '( - ("jan") ("feb") ("mar") ("apr") ("may") ("jun") ("jul") ("aug") - ("sep") ("oct") ("nov") ("dec") - ("acmcs") ("acta") ("cacm") ("ibmjrd") ("ibmsj") ("ieeese") - ("ieeetc") ("ieeetcad") ("ipl") ("jacm") ("jcss") ("scp") - ("sicomp") ("tcs") ("tocs") ("tods") ("tog") ("toms") ("toois") - ("toplas") - ) - "Alist of string definitions. -Should contain the strings defined in the BibTeX style files. Each -element is a list with just one element: the string.") - -(defvar bibtex-string-files nil - "*List of BibTeX files containing string definitions. -Those files must be specified using pathnames relative to the -directories specified in $BIBINPUTS. This variable is only evaluated -when bibtex-mode is entered (i. e. when loading the BibTeX file).") - -(defvar bibtex-help-message t - "*If not nil print help messages in the echo area on entering a new field.") - -(defvar bibtex-autokey-names 1 - "*Number of names to use for the automatically generated reference key. -If this is set to anything but a number, all names are used. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-name-change-strings - '(("\\\\\\\"a" "ae") ("\\\\\\\"o" "oe") ("\\\\\\\"u" "ue") - ("\\\\\\\"s" "ss") - ("\\\\\\\"A" "Ae") ("\\\\\\\"O" "Oe") ("\\\\\\\"U" "Ue") - ("{" "") ("}" "")) - "Alist of (old-regexp new-string) pairs. -Any part of name matching a old-regexp is replaced by new-string. -Case of the old-regexp is significant. All regexps are tried in the -order in which they appear in the list, so be sure to avoid recursion here. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-name-length 'infty - "*Number of characters from name to incorporate into key. + '(("Article" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the article (BibTeX converts it to lowercase)") + ("journal" "Name of the journal (use string, remove braces)") + ("year" "Year of publication")) + (("volume" "Volume of the journal") + ("number" "Number of the journal (only allowed if entry contains volume)") + ("pages" "Pages in the journal") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem"))) + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the article (BibTeX converts it to lowercase)")) + (("pages" "Pages in the journal") + ("journal" "Name of the journal (use string, remove braces)") + ("year" "Year of publication") + ("volume" "Volume of the journal") + ("number" "Number of the journal") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("Book" + ((("author" "Author1 [and Author2 ...] [and others]" nil t) + ("editor" "Editor1 [and Editor2 ...] [and others]" nil t) + ("title" "Title of the book") + ("publisher" "Publishing company") + ("year" "Year of publication")) + (("volume" "Volume of the book in the series") + ("number" "Number of the book in a small series (overwritten by volume)") + ("series" "Series in which the book appeared") + ("address" "Address of the publisher") + ("edition" "Edition of the book as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem"))) + ((("author" "Author1 [and Author2 ...] [and others]" nil t) + ("editor" "Editor1 [and Editor2 ...] [and others]" nil t) + ("title" "Title of the book")) + (("publisher" "Publishing company") + ("year" "Year of publication") + ("volume" "Volume of the book in the series") + ("number" "Number of the book in a small series (overwritten by volume)") + ("series" "Series in which the book appeared") + ("address" "Address of the publisher") + ("edition" "Edition of the book as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("Booklet" + ((("title" "Title of the booklet (BibTeX converts it to lowercase)")) + (("author" "Author1 [and Author2 ...] [and others]") + ("howpublished" "The way in which the booklet was published") + ("address" "Address of the publisher") + ("month" "Month of the publication as a string (remove braces)") + ("year" "Year of publication") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("InBook" + ((("author" "Author1 [and Author2 ...] [and others]" nil t) + ("editor" "Editor1 [and Editor2 ...] [and others]" nil t) + ("title" "Title of the book") + ("chapter" "Chapter in the book") + ("publisher" "Publishing company") + ("year" "Year of publication")) + (("volume" "Volume of the book in the series") + ("number" "Number of the book in a small series (overwritten by volume)") + ("series" "Series in which the book appeared") + ("type" "Word to use instead of \"chapter\"") + ("address" "Address of the publisher") + ("edition" "Edition of the book as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("pages" "Pages in the book") + ("note" "Remarks to be put at the end of the \\bibitem"))) + ((("author" "Author1 [and Author2 ...] [and others]" nil t) + ("editor" "Editor1 [and Editor2 ...] [and others]" nil t) + ("title" "Title of the book") + ("chapter" "Chapter in the book")) + (("pages" "Pages in the book") + ("publisher" "Publishing company") + ("year" "Year of publication") + ("volume" "Volume of the book in the series") + ("number" "Number of the book in a small series (overwritten by volume)") + ("series" "Series in which the book appeared") + ("type" "Word to use instead of \"chapter\"") + ("address" "Address of the publisher") + ("edition" "Edition of the book as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("InCollection" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the article in book (BibTeX converts it to lowercase)") + ("booktitle" "Name of the book") + ("publisher" "Publishing company") + ("year" "Year of publication")) + (("editor" "Editor1 [and Editor2 ...] [and others]") + ("volume" "Volume of the book in the series") + ("number" "Number of the book in a small series (overwritten by volume)") + ("series" "Series in which the book appeared") + ("type" "Word to use instead of \"chapter\"") + ("chapter" "Chapter in the book") + ("pages" "Pages in the book") + ("address" "Address of the publisher") + ("edition" "Edition of the book as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem"))) + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the article in book (BibTeX converts it to lowercase)") + ("booktitle" "Name of the book")) + (("pages" "Pages in the book") + ("publisher" "Publishing company") + ("year" "Year of publication") + ("editor" "Editor1 [and Editor2 ...] [and others]") + ("volume" "Volume of the book in the series") + ("number" "Number of the book in a small series (overwritten by volume)") + ("series" "Series in which the book appeared") + ("type" "Word to use instead of \"chapter\"") + ("chapter" "Chapter in the book") + ("address" "Address of the publisher") + ("edition" "Edition of the book as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("InProceedings" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the article in proceedings (BibTeX converts it to lowercase)") + ("booktitle" "Name of the conference proceedings") + ("year" "Year of publication")) + (("editor" "Editor1 [and Editor2 ...] [and others]") + ("volume" "Volume of the conference proceedings in the series") + ("number" "Number of the conference proceedings in a small series (overwritten by volume)") + ("series" "Series in which the conference proceedings appeared") + ("pages" "Pages in the conference proceedings") + ("address" "Location of the Proceedings") + ("month" "Month of the publication as a string (remove braces)") + ("organization" "Sponsoring organization of the conference") + ("publisher" "Publishing company, its location") + ("note" "Remarks to be put at the end of the \\bibitem"))) + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the article in proceedings (BibTeX converts it to lowercase)")) + (("booktitle" "Name of the conference proceedings") + ("pages" "Pages in the conference proceedings") + ("year" "Year of publication") + ("editor" "Editor1 [and Editor2 ...] [and others]") + ("volume" "Volume of the conference proceedings in the series") + ("number" "Number of the conference proceedings in a small series (overwritten by volume)") + ("series" "Series in which the conference proceedings appeared") + ("address" "Location of the Proceedings") + ("month" "Month of the publication as a string (remove braces)") + ("organization" "Sponsoring organization of the conference") + ("publisher" "Publishing company, its location") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("Manual" + ((("title" "Title of the manual")) + (("author" "Author1 [and Author2 ...] [and others]") + ("organization" "Publishing organization of the manual") + ("address" "Address of the organization") + ("edition" "Edition of the manual as a capitalized English word") + ("month" "Month of the publication as a string (remove braces)") + ("year" "Year of publication") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("MastersThesis" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the master\'s thesis (BibTeX converts it to lowercase)") + ("school" "School where the master\'s thesis was written") + ("year" "Year of publication")) + (("type" "Type of the master\'s thesis (if other than \"Master\'s thesis\")") + ("address" "Address of the school (if not part of field \"school\") or country") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("Misc" + (() + (("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the work (BibTeX converts it to lowercase)") + ("howpublished" "The way in which the work was published") + ("month" "Month of the publication as a string (remove braces)") + ("year" "Year of publication") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("PhdThesis" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the PhD. thesis") + ("school" "School where the PhD. thesis was written") + ("year" "Year of publication")) + (("type" "Type of the PhD. thesis") + ("address" "Address of the school (if not part of field \"school\") or country") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("Proceedings" + ((("title" "Title of the conference proceedings") + ("year" "Year of publication")) + (("booktitle" "Title of the proceedings for cross references") + ("editor" "Editor1 [and Editor2 ...] [and others]") + ("volume" "Volume of the conference proceedings in the series") + ("number" "Number of the conference proceedings in a small series (overwritten by volume)") + ("series" "Series in which the conference proceedings appeared") + ("address" "Location of the Proceedings") + ("month" "Month of the publication as a string (remove braces)") + ("organization" "Sponsoring organization of the conference") + ("publisher" "Publishing company, its location") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("TechReport" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the technical report (BibTeX converts it to lowercase)") + ("institution" "Sponsoring institution of the report") + ("year" "Year of publication")) + (("type" "Type of the report (if other than \"technical report\")") + ("number" "Number of the technical report") + ("address" "Address of the institution (if not part of field \"institution\") or country") + ("month" "Month of the publication as a string (remove braces)") + ("note" "Remarks to be put at the end of the \\bibitem")))) + ("Unpublished" + ((("author" "Author1 [and Author2 ...] [and others]") + ("title" "Title of the unpublished work (BibTeX converts it to lowercase)") + ("note" "Remarks to be put at the end of the \\bibitem")) + (("month" "Month of the publication as a string (remove braces)") + ("year" "Year of publication"))))) + + "List of BibTeX entry types and their associated fields. +List elements are triples +\(ENTRY-NAME (REQUIRED OPTIONAL) (CROSSREF-REQUIRED CROSSREF-OPTIONAL)). +ENTRY-NAME is the name of a BibTeX entry. The remaining pairs contain +the required and optional fields of the BibTeX entry. +The second pair is used if a crossref field is present +and the first pair is used if a crossref field is absent. +If the second pair is nil, the first pair is always used. +REQUIRED, OPTIONAL, CROSSREF-REQUIRED and CROSSREF-OPTIONAL are lists. +Each element of these lists is a list of the form +\(FIELD-NAME COMMENT-STRING INIT ALTERNATIVE-FLAG). +COMMENT-STRING, INIT, and ALTERNATIVE-FLAG are optional. +FIELD-NAME is the name of the field, COMMENT-STRING is the comment that +appears in the echo area, INIT is either the initial content of the +field or a function, which is called to determine the initial content +of the field, and ALTERNATIVE-FLAG (either nil or t) marks if the +field is an alternative. ALTERNATIVE-FLAG may be t only in the +REQUIRED or CROSSREF-REQUIRED lists.") +(put 'bibtex-entry-field-alist 'risky-local-variable t) + +(defcustom bibtex-comment-start "@Comment" + "String starting a BibTeX comment." + :group 'bibtex + :type 'string) + +(defcustom bibtex-add-entry-hook nil + "List of functions to call when BibTeX entry has been inserted." + :group 'bibtex + :type 'hook) + +(defcustom bibtex-predefined-month-strings + '(("jan" . "January") + ("feb" . "February") + ("mar" . "March") + ("apr" . "April") + ("may" . "May") + ("jun" . "June") + ("jul" . "July") + ("aug" . "August") + ("sep" . "September") + ("oct" . "October") + ("nov" . "November") + ("dec" . "December")) + "Alist of month string definitions used in the BibTeX style files. +Each element is a pair of strings (ABBREVIATION . EXPANSION)." + :group 'bibtex + :type '(repeat (cons (string :tag "Month abbreviation") + (string :tag "Month expansion")))) + +(defcustom bibtex-predefined-strings + (append + bibtex-predefined-month-strings + '(("acmcs" . "ACM Computing Surveys") + ("acta" . "Acta Informatica") + ("cacm" . "Communications of the ACM") + ("ibmjrd" . "IBM Journal of Research and Development") + ("ibmsj" . "IBM Systems Journal") + ("ieeese" . "IEEE Transactions on Software Engineering") + ("ieeetc" . "IEEE Transactions on Computers") + ("ieeetcad" . "IEEE Transactions on Computer-Aided Design of Integrated Circuits") + ("ipl" . "Information Processing Letters") + ("jacm" . "Journal of the ACM") + ("jcss" . "Journal of Computer and System Sciences") + ("scp" . "Science of Computer Programming") + ("sicomp" . "SIAM Journal on Computing") + ("tcs" . "Theoretical Computer Science") + ("tocs" . "ACM Transactions on Computer Systems") + ("tods" . "ACM Transactions on Database Systems") + ("tog" . "ACM Transactions on Graphics") + ("toms" . "ACM Transactions on Mathematical Software") + ("toois" . "ACM Transactions on Office Information Systems") + ("toplas" . "ACM Transactions on Programming Languages and Systems"))) + "Alist of string definitions used in the BibTeX style files. +Each element is a pair of strings (ABBREVIATION . EXPANSION)." + :group 'bibtex + :type '(repeat (cons (string :tag "String") + (string :tag "String expansion")))) + +(defcustom bibtex-string-files nil + "List of BibTeX files containing string definitions. +List elements can be absolute file names or file names relative +to the directories specified in `bibtex-string-file-path'." + :group 'bibtex + :type '(repeat file)) + +(defvar bibtex-string-file-path (getenv "BIBINPUTS") + "*Colon separated list of paths to search for `bibtex-string-files'.") + +(defcustom bibtex-files nil + "List of BibTeX files that are searched for entry keys. +List elements can be absolute file names or file names relative to the +directories specified in `bibtex-file-path'. If an element is a directory, +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)) + +(defvar bibtex-file-path (getenv "BIBINPUTS") + "*Colon separated list of paths to search for `bibtex-files'.") + +(defcustom bibtex-help-message t + "If non-nil print help messages in the echo area on entering a new field." + :group 'bibtex + :type 'boolean) + +(defcustom bibtex-autokey-prefix-string "" + "String prefix for automatically generated reference keys. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'string) + +(defcustom bibtex-autokey-names 1 + "Number of names to use for the automatically generated reference key. +Possibly more names are used according to `bibtex-autokey-names-stretch'. +If this variable is nil, all names are used. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(choice (const :tag "All" infty) + integer)) + +(defcustom bibtex-autokey-names-stretch 0 + "Number of names that can additionally be used for reference keys. +These names are used only, if all names are used then. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'integer) + +(defcustom bibtex-autokey-additional-names "" + "String to append to the generated key if not all names could be used. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'string) + +(defcustom bibtex-autokey-expand-strings nil + "If non-nil, expand strings when extracting the content of a BibTeX field. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'boolean) + +(defvar bibtex-autokey-transcriptions + '(;; language specific characters + ("\\\\aa" . "a") ; \aa -> a + ("\\\\AA" . "A") ; \AA -> A + ("\\\"a\\|\\\\\\\"a\\|\\\\ae" . "ae") ; "a,\"a,\ae -> ae + ("\\\"A\\|\\\\\\\"A\\|\\\\AE" . "Ae") ; "A,\"A,\AE -> Ae + ("\\\\i" . "i") ; \i -> i + ("\\\\j" . "j") ; \j -> j + ("\\\\l" . "l") ; \l -> l + ("\\\\L" . "L") ; \L -> L + ("\\\"o\\|\\\\\\\"o\\|\\\\o\\|\\\\oe" . "oe") ; "o,\"o,\o,\oe -> oe + ("\\\"O\\|\\\\\\\"O\\|\\\\O\\|\\\\OE" . "Oe") ; "O,\"O,\O,\OE -> Oe + ("\\\"s\\|\\\\\\\"s\\|\\\\3" . "ss") ; "s,\"s,\3 -> ss + ("\\\"u\\|\\\\\\\"u" . "ue") ; "u,\"u -> ue + ("\\\"U\\|\\\\\\\"U" . "Ue") ; "U,\"U -> Ue + ;; accents + ("\\\\`\\|\\\\'\\|\\\\\\^\\|\\\\~\\|\\\\=\\|\\\\\\.\\|\\\\u\\|\\\\v\\|\\\\H\\|\\\\t\\|\\\\c\\|\\\\d\\|\\\\b" . "") + ;; braces, quotes, concatenation. + ("[`'\"{}#]" . "") + ;; spaces + ("\\\\?[ \t\n]+\\|~" . " ")) + "Alist of (OLD-REGEXP . NEW-STRING) pairs. +Used by the default values of `bibtex-autokey-name-change-strings' and +`bibtex-autokey-titleword-change-strings'. Defaults to translating some +language specific characters to their ASCII transcriptions, and +removing any character accents.") + +(defcustom bibtex-autokey-name-change-strings + bibtex-autokey-transcriptions + "Alist of (OLD-REGEXP . NEW-STRING) pairs. +Any part of a name matching OLD-REGEXP is replaced by NEW-STRING. +Case is significant in OLD-REGEXP. All regexps are tried in the +order in which they appear in the list. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(repeat (cons (regexp :tag "Old") + (string :tag "New")))) + +(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 + :type '(choice (const :tag "Preserve case" identity) + (const :tag "Downcase" downcase) + (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. If this is set to anything but a number, all characters are used. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-name-separator "" - "*String that comes between any two names in the key. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-year-length 2 - "*Number of rightmost digits from the year field yo incorporate into key. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-titlewords 5 - "*Number of title words to use for the automatically generated reference key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(choice (const :tag "All" infty) + integer)) + +(defcustom bibtex-autokey-name-separator "" + "String that comes between any two names in the key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'string) + +(defcustom bibtex-autokey-year-length 2 + "Number of rightmost digits from the year field to incorporate into key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'integer) + +(defcustom bibtex-autokey-use-crossref t + "If non-nil use fields from crossreferenced entry if necessary. +If this variable is non-nil and some field has no entry, but a +valid crossref entry, the field from the crossreferenced entry is used. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'boolean) + +(defcustom bibtex-autokey-titlewords 5 + "Number of title words to use for the automatically generated reference key. If this is set to anything but a number, all title words are used. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-title-terminators - '("\\." "!" "\\?" ":" ";" "---") - "*Regexp list defining the termination of the main part of the title. -Case of the regexps is ignored. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-titlewords-stretch 2 - "*Number of words that can additionally be used from the title. +Possibly more words from the title are used according to +`bibtex-autokey-titlewords-stretch'. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(choice (const :tag "All" infty) + integer)) + +(defcustom bibtex-autokey-title-terminators "[.!?:;]\\|--" + "Regexp defining the termination of the main part of the title. +Case of the regexps is ignored. See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'regexp) + +(defcustom bibtex-autokey-titlewords-stretch 2 + "Number of words that can additionally be used from the title. These words are used only, if a sentence from the title can be ended then. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-titleword-first-ignore - '("a" "an" "on" "the" "eine?" "der" "die" "das") - "*Determines words that may begin a title but are not to be used in the key. -Each item of the list is a regexp. If the first word of the title matchs a -regexp from that list, it is not included in the title, even if it is -capitalized. Regexps in the list must be entered using lowercase letters.") - -(defvar bibtex-autokey-titleword-abbrevs nil - "*Determines exceptions to the usual abbreviation mechanism. -A list of (old-regexp new-string) pairs. -Use all lowercase letters for old-regexp. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-titleword-change-strings - '(("\\\\\\\"a" "ae") ("\\\\\\\"o" "oe") ("\\\\\\\"u" "ue") - ("\\\\\\\"s" "ss") - ("\\\\\\\"A" "Ae") ("\\\\\\\"O" "Oe") ("\\\\\\\"U" "Ue") - ("{" "") ("}" "")) - "Alist of (old-regexp new-string) pairs. -Any part of title word matching a old-regexp is replaced by new-string. -Case of the old-regexp is significant. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-titleword-length 5 - "*Number of characters from title words to incorporate into key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'integer) + +(defcustom bibtex-autokey-titleword-ignore + '("A" "An" "On" "The" "Eine?" "Der" "Die" "Das" + "[^[:upper:]].*" ".*[^[:upper:]0-9].*") + "Determines words from the title that are not to be used in the key. +Each item of the list is a regexp. If a word of the title matches a +regexp from that list, it is not included in the title part of the key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(repeat regexp)) + +(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 + :type '(choice (const :tag "Preserve case" identity) + (const :tag "Downcase" downcase) + (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. +An alist of (OLD-REGEXP . NEW-STRING) pairs. Case is ignored +in matching against OLD-REGEXP, and the first matching pair is used. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(repeat (cons (regexp :tag "Old") + (string :tag "New")))) + +(defcustom bibtex-autokey-titleword-change-strings + bibtex-autokey-transcriptions + "Alist of (OLD-REGEXP . NEW-STRING) pairs. +Any part of title word matching a OLD-REGEXP is replaced by NEW-STRING. +Case is significant in OLD-REGEXP. All regexps are tried in the +order in which they appear in the list. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(repeat (cons (regexp :tag "Old") + (string :tag "New")))) + +(defcustom bibtex-autokey-titleword-length 5 + "Number of characters from title words to incorporate into key. If this is set to anything but a number, all characters are used. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-titleword-separator "_" - "*String to be put between the title words. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-name-year-separator "" - "*String to be put between name part and year part of key. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-year-title-separator ":_" - "*String to be put between name part and year part of key. -See the documentation of function bibtex-generate-autokey for further detail.") - -(defvar bibtex-autokey-edit-before-use t - "*If non-nil, user is allowed to edit the generated key before it is used.") - -(defvar bibtex-font-lock-keywords - (list - "^@[A-Za-z]*[({]" - ;; reference type - '("^\\([ \t]*OPT[A-Za-z_-][A-Za-z0-9_-]*\\)[ \t]*=" - 1 font-lock-comment-face) - ;; optional field names - '("^\\([ \t]*[A-Za-z_-][A-Za-z0-9_-]*\\)[ \t]*=" - 1 font-lock-function-name-face) - ;; field names - '("^@[A-Za-z]*[({]\\([^\n,]*\\)," - 1 font-lock-string-face) - ;; reference labels - ) - "*Fonts to use in BibTeX mode") +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type '(choice (const :tag "All" infty) + integer)) + +(defcustom bibtex-autokey-titleword-separator "_" + "String to be put between the title words. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'string) + +(defcustom bibtex-autokey-name-year-separator "" + "String to be put between name part and year part of key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'string) + +(defcustom bibtex-autokey-year-title-separator ":_" + "String to be put between name part and year part of key. +See `bibtex-generate-autokey' for details." + :group 'bibtex-autokey + :type 'string) + +(defcustom bibtex-autokey-edit-before-use t + "If non-nil, user is allowed to edit the generated key before it is used." + :group 'bibtex-autokey + :type 'boolean) + +(defcustom bibtex-autokey-before-presentation-function nil + "If non-nil, function to call before generated key is presented. +The function must take one argument (the automatically generated key), +and must return a string (the key to use)." + :group 'bibtex-autokey + :type '(choice (const nil) function)) + +(defcustom bibtex-entry-offset 0 + "Offset for BibTeX entries. +Added to the value of all other variables which determine columns." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-field-indentation 2 + "Starting column for the name part in BibTeX fields." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-text-indentation + (+ bibtex-field-indentation + (length "organization = ")) + "Starting column for the text part in BibTeX fields. +Should be equal to the space needed for the longest name part." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-contline-indentation + (+ bibtex-text-indentation 1) + "Starting column for continuation lines of BibTeX fields." + :group 'bibtex + :type 'integer) + +(defcustom bibtex-align-at-equal-sign nil + "If non-nil, align fields at equal sign instead of field text. +If non-nil, the column for the equal sign is the value of +`bibtex-text-indentation', minus 2." + :group 'bibtex + :type 'boolean) + +(defcustom bibtex-comma-after-last-field nil + "If non-nil, a comma is put at end of last field in the entry template." + :group 'bibtex + :type 'boolean) + +(defcustom bibtex-autoadd-commas t + "If non-nil automatically add missing commas at end of BibTeX fields." + :group 'bibtex + :type 'boolean) + +(defcustom bibtex-autofill-types '("Proceedings") + "Automatically fill fields if possible for those BibTeX entry types." + :group 'bibtex + :type '(repeat string)) + +(defcustom bibtex-summary-function 'bibtex-summary + "Function to call for generating a summary of current BibTeX entry. +It takes no arguments. Point must be at beginning of entry. +Used by `bibtex-complete-crossref-cleanup' and `bibtex-copy-summary-as-kill'." + :group 'bibtex + :type '(choice (const :tag "Default" bibtex-summary) + (function :tag "Personalized function"))) + +(defcustom bibtex-generate-url-list + '((("url" . ".*:.*"))) + "List of schemes for generating the URL of a BibTeX entry. +These schemes are used by `bibtex-url'. + +Each scheme should have one of these forms: + + ((FIELD . REGEXP)) + ((FIELD . REGEXP) STEP...) + ((FIELD . REGEXP) STRING STEP...) + +FIELD is a field name as returned by `bibtex-parse-entry'. +REGEXP is matched against the text of FIELD. If the match succeeds, +then this scheme is used. If no STRING and STEPs are specified +the matched text is used as the URL, otherwise the URL is built +by evaluating STEPs. If no STRING is specified the STEPs must result +in strings which are concatenated. Otherwise the resulting objects +are passed through `format' using STRING as format control string. + +A STEP is a list (FIELD REGEXP REPLACE). The text of FIELD +is matched against REGEXP, and is replaced with REPLACE. +REPLACE can be a string, or a number (which selects the corresponding +submatch), or a function called with the field's text as argument +and with the `match-data' properly set. + +Case is always ignored. Always remove the field delimiters. +If `bibtex-expand-strings' is non-nil, BibTeX strings are expanded +for generating the URL. + +The following is a complex example, see http://link.aps.org/linkfaq.html. + + (((\"journal\" . \"\\\\=<\\(PR[ABCDEL]?\\|RMP\\)\\\\=>\") + \"http://link.aps.org/abstract/%s/v%s/p%s\" + (\"journal\" \".*\" downcase) + (\"volume\" \".*\" 0) + (\"pages\" \"\\`[A-Z]?[0-9]+\" 0)))" + :group 'bibtex + :type '(repeat + (cons :tag "Scheme" + (cons :tag "Matcher" :extra-offset 4 + (string :tag "BibTeX field") + (regexp :tag "Regexp")) + (choice + (const :tag "Take match as is" nil) + (cons :tag "Formatted" + (string :tag "Format control string") + (repeat :tag "Steps to generate URL" + (list (string :tag "BibTeX field") + (regexp :tag "Regexp") + (choice (string :tag "Replacement") + (integer :tag "Sub-match") + (function :tag "Filter"))))) + (repeat :tag "Concatenated" + (list (string :tag "BibTeX field") + (regexp :tag "Regexp") + (choice (string :tag "Replacement") + (integer :tag "Sub-match") + (function :tag "Filter")))))))) +(put 'bibtex-generate-url-list 'risky-local-variable t) + +(defcustom bibtex-expand-strings nil + "If non-nil, expand strings when extracting the content of a BibTeX field." + :group 'bibtex + :type 'boolean) + +;; `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. -;; Syntax Table, Keybindings and BibTeX Entry List +;; Syntax Table and Keybindings (defvar bibtex-mode-syntax-table (let ((st (make-syntax-table))) - ;; [alarson:19920214.1004CST] make double quote a string quote (modify-syntax-entry ?\" "\"" st) (modify-syntax-entry ?$ "$$ " st) (modify-syntax-entry ?% "< " st) - (modify-syntax-entry ?' "w " st) - (modify-syntax-entry ?@ "w " st) + (modify-syntax-entry ?' "w " st) + (modify-syntax-entry ?@ "w " st) (modify-syntax-entry ?\\ "\\" st) (modify-syntax-entry ?\f "> " st) (modify-syntax-entry ?\n "> " st) + ;; Keys cannot have = in them (wrong font-lock of @string{foo=bar}). + (modify-syntax-entry ?= "." st) (modify-syntax-entry ?~ " " st) - st)) + st) + "Syntax table used in BibTeX mode buffers.") (defvar bibtex-mode-map (let ((km (make-sparse-keymap))) - + ;; The Key `C-c&' is reserved for reftex.el (define-key km "\t" 'bibtex-find-text) (define-key km "\n" 'bibtex-next-field) - (define-key km "\M-\t" 'bibtex-complete-string) - (define-key km "\C-c\"" 'bibtex-remove-double-quotes-or-braces) - (define-key km "\C-c{" 'bibtex-remove-double-quotes-or-braces) - (define-key km "\C-c}" 'bibtex-remove-double-quotes-or-braces) + (define-key km "\M-\t" 'bibtex-complete) + (define-key km "\C-c\"" 'bibtex-remove-delimiters) + (define-key km "\C-c{" 'bibtex-remove-delimiters) + (define-key km "\C-c}" 'bibtex-remove-delimiters) (define-key km "\C-c\C-c" 'bibtex-clean-entry) + (define-key km "\C-c\C-q" 'bibtex-fill-entry) + (define-key km "\C-c\C-s" 'bibtex-find-entry) + (define-key km "\C-c\C-x" 'bibtex-find-crossref) + (define-key km "\C-c\C-t" 'bibtex-copy-summary-as-kill) (define-key km "\C-c?" 'bibtex-print-help-message) (define-key km "\C-c\C-p" 'bibtex-pop-previous) (define-key km "\C-c\C-n" 'bibtex-pop-next) - (define-key km "\C-c\C-k" 'bibtex-kill-optional-field) + (define-key km "\C-c\C-k" 'bibtex-kill-field) + (define-key km "\C-c\M-k" 'bibtex-copy-field-as-kill) + (define-key km "\C-c\C-w" 'bibtex-kill-entry) + (define-key km "\C-c\M-w" 'bibtex-copy-entry-as-kill) + (define-key km "\C-c\C-y" 'bibtex-yank) + (define-key km "\C-c\M-y" 'bibtex-yank-pop) (define-key km "\C-c\C-d" 'bibtex-empty-field) - (define-key km "\C-c$" 'bibtex-ispell-entry) - (define-key km "\M-\C-a" 'bibtex-beginning-of-entry) - (define-key km "\M-\C-e" 'bibtex-end-of-entry) - (define-key km "\C-c\C-b" 'bibtex-entry) - (define-key km "\C-c\C-q" 'bibtex-hide-entry-bodies) + (define-key km "\C-c\C-f" 'bibtex-make-field) + (define-key km "\C-c\C-u" 'bibtex-entry-update) + (define-key km "\C-c$" 'bibtex-ispell-abstract) + (define-key km "\M-\C-a" 'bibtex-beginning-of-entry) + (define-key km "\M-\C-e" 'bibtex-end-of-entry) + (define-key km "\C-\M-l" 'bibtex-reposition-window) + (define-key km "\C-\M-h" 'bibtex-mark-entry) + (define-key km "\C-c\C-b" 'bibtex-entry) (define-key km "\C-c\C-rn" 'bibtex-narrow-to-entry) (define-key km "\C-c\C-rw" 'widen) - (define-key km "\C-c\C-o" 'bibtex-remove-OPT) - + (define-key km "\C-c\C-l" 'bibtex-url) + (define-key km "\C-c\C-o" 'bibtex-remove-OPT-or-ALT) (define-key km "\C-c\C-e\C-i" 'bibtex-InProceedings) (define-key km "\C-c\C-ei" 'bibtex-InCollection) (define-key km "\C-c\C-eI" 'bibtex-InBook) @@ -476,1828 +949,3462 @@ See the documentation of function bibtex-generate-autokey for further detail.") (define-key km "\C-c\C-e\C-p" 'bibtex-InProceedings) (define-key km "\C-c\C-ep" 'bibtex-Proceedings) (define-key km "\C-c\C-eP" 'bibtex-PhdThesis) - (define-key km "\C-c\C-e\M-p" 'bibtex-preamble) - (define-key km "\C-c\C-e\C-s" 'bibtex-string) + (define-key km "\C-c\C-e\M-p" 'bibtex-Preamble) + (define-key km "\C-c\C-e\C-s" 'bibtex-String) (define-key km "\C-c\C-e\C-t" 'bibtex-TechReport) (define-key km "\C-c\C-e\C-u" 'bibtex-Unpublished) - km)) - -(define-key bibtex-mode-map [menu-bar bibtex-edit] - (cons "BibTeX-Edit" (make-sparse-keymap "BibTeX-Edit"))) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-print-help-message] - '("Help about Current Field" . bibtex-print-help-message)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-complete-string] - '("String Complete" . bibtex-complete-string)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-next-field] - '("Next Field" . bibtex-next-field)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-find-text] - '("End of Field" . bibtex-find-text)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-pop-previous] - '("Snatch from Similar Preceding Field" . bibtex-pop-previous)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-pop-next] - '("Snatch from Similar Following Field" . bibtex-pop-next)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-remove-OPT] - '("Remove OPT" . bibtex-remove-OPT)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-remove-double-quotes-or-braces] - '("Remove Quotes or Braces" . bibtex-remove-double-quotes-or-braces)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-clean-entry] - '("Clean Up Entry" . bibtex-clean-entry)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-sort-entries] - '("Sort Entries" . bibtex-sort-entries)) -(define-key bibtex-mode-map [menu-bar bibtex-edit bibtex-validate-buffer] - '("Validate Entries" . bibtex-validate-buffer)) - -(define-key bibtex-mode-map [menu-bar entry-types] - (cons "Entry-Types" (make-sparse-keymap "Entry-Types"))) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-preamble] - '("Preamble" . bibtex-preamble)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-string] - '("String" . bibtex-string)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Misc] - '("Miscellaneous" . bibtex-Misc)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Unpublished] - '("Unpublished" . bibtex-Unpublished)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Manual] - '("Technical Manual" . bibtex-Manual)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-TechReport] - '("Technical Report" . bibtex-TechReport)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-MastersThesis] - '("Master's Thesis" . bibtex-MastersThesis)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-PhdThesis] - '("PhD. Thesis" . bibtex-PhdThesis)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Booklet] - '("Booklet (Bound, but no Publisher/Institution)" . bibtex-Booklet)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Book] - '("Book" . bibtex-Book)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Proceedings] - '("Conference Proceedings" . bibtex-Proceedings)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-InBook] - '("Chapter or Pages in a Book" . bibtex-InBook)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-InCollection] - '("Article in a Collection" . bibtex-InCollection)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-InProceedings] - '("Article in Conference Proceedings" . bibtex-InProceedings)) -(define-key bibtex-mode-map [menu-bar entry-types bibtex-Article] - '("Article in Journal" . bibtex-Article)) - + km) + "Keymap used in BibTeX mode.") + +(easy-menu-define + bibtex-edit-menu bibtex-mode-map "BibTeX-Edit Menu in BibTeX mode" + '("BibTeX-Edit" + ("Moving inside an Entry" + ["End of Field" bibtex-find-text t] + ["Next Field" bibtex-next-field t] + ["Beginning of Entry" bibtex-beginning-of-entry t] + ["End of Entry" bibtex-end-of-entry t] + "--" + ["Make Entry Visible" bibtex-reposition-window t]) + ("Moving in BibTeX Buffers" + ["Find Entry" bibtex-find-entry t] + ["Find Crossref Entry" bibtex-find-crossref t]) + "--" + ("Operating on Current Field" + ["Fill Field" fill-paragraph t] + ["Remove Delimiters" bibtex-remove-delimiters t] + ["Remove OPT or ALT Prefix" bibtex-remove-OPT-or-ALT t] + ["Clear Field" bibtex-empty-field t] + "--" + ["Kill Field" bibtex-kill-field t] + ["Copy Field to Kill Ring" bibtex-copy-field-as-kill t] + ["Paste Most Recently Killed Field" bibtex-yank t] + ["Paste Previously Killed Field" bibtex-yank-pop t] + "--" + ["Make New Field" bibtex-make-field t] + "--" + ["Snatch from Similar Following Field" bibtex-pop-next t] + ["Snatch from Similar Preceding Field" bibtex-pop-previous t] + "--" + ["String or Key Complete" bibtex-complete t] + "--" + ["Help about Current Field" bibtex-print-help-message t]) + ("Operating on Current Entry" + ["Fill Entry" bibtex-fill-entry t] + ["Clean Entry" bibtex-clean-entry t] + ["Update Entry" bibtex-entry-update t] + "--" + ["Kill Entry" bibtex-kill-entry t] + ["Copy Entry to Kill Ring" bibtex-copy-entry-as-kill t] + ["Paste Most Recently Killed Entry" bibtex-yank t] + ["Paste Previously Killed Entry" bibtex-yank-pop t] + "--" + ["Copy Summary to Kill Ring" bibtex-copy-summary-as-kill t] + ["Browse URL" bibtex-url t] + "--" + ["Ispell Entry" bibtex-ispell-entry t] + ["Ispell Entry Abstract" bibtex-ispell-abstract t] + "--" + ["Narrow to Entry" bibtex-narrow-to-entry t] + ["Mark Entry" bibtex-mark-entry t] + "--" + ["View Cite Locations (RefTeX)" reftex-view-crossref-from-bibtex + (fboundp 'reftex-view-crossref-from-bibtex)]) + ("Operating on Buffer or Region" + ["Validate Entries" bibtex-validate t] + ["Sort Entries" bibtex-sort-buffer t] + ["Reformat Entries" bibtex-reformat t] + ["Count Entries" bibtex-count-entries t] + "--" + ["Convert Alien Buffer" bibtex-convert-alien t]) + ("Operating on Multiple Buffers" + ["Validate Entries" bibtex-validate-globally t]))) + +(easy-menu-define + bibtex-entry-menu bibtex-mode-map "Entry-Types Menu in BibTeX mode" + (list "Entry-Types" + ["Article in Journal" bibtex-Article t] + ["Article in Conference Proceedings" bibtex-InProceedings t] + ["Article in a Collection" bibtex-InCollection t] + ["Chapter or Pages in a Book" bibtex-InBook t] + ["Conference Proceedings" bibtex-Proceedings t] + ["Book" bibtex-Book t] + ["Booklet (Bound, but no Publisher/Institution)" bibtex-Booklet t] + ["PhD. Thesis" bibtex-PhdThesis t] + ["Master's Thesis" bibtex-MastersThesis t] + ["Technical Report" bibtex-TechReport t] + ["Technical Manual" bibtex-Manual t] + ["Unpublished" bibtex-Unpublished t] + ["Miscellaneous" bibtex-Misc t] + "--" + ["String" bibtex-String t] + ["Preamble" bibtex-Preamble t])) ;; Internal Variables -(defvar bibtex-pop-previous-search-point nil) -;; Next point where bibtex-pop-previous starts looking for a similar -;; entry. +(defvar bibtex-pop-previous-search-point nil + "Next point where `bibtex-pop-previous' starts looking for a similar entry.") -(defvar bibtex-pop-next-search-point nil) -;; Next point where bibtex-pop-next starts looking for a similar entry. +(defvar bibtex-pop-next-search-point nil + "Next point where `bibtex-pop-next' starts looking for a similar entry.") -(defvar bibtex-completion-candidates nil) -;; Candidates for bibtex-complete-string. Initialized from -;; bibtex-predefined-strings and bibtex-string-files. This variable is -;; buffer-local. -(make-variable-buffer-local 'bibtex-completion-candidates) +(defvar bibtex-field-kill-ring nil + "Ring of least recently killed fields. +At most `bibtex-field-kill-ring-max' items are kept here.") -(defvar bibtex-keys nil) -;; Candidates for TAB completion when entering a reference key using -;; the minibuffer. Initialized in bibtex-mode and updated for each -;; new entry. This variable is buffer-local. -(make-variable-buffer-local 'bibtex-keys) +(defvar bibtex-field-kill-ring-yank-pointer nil + "The tail of `bibtex-field-kill-ring' whose car is the last item yanked.") -(defvar bibtex-buffer-last-parsed-for-keys-tick nil) -;; Remembers the value returned by buffer-modified-tick when buffer -;; was parsed for keys the last time. -(make-variable-buffer-local 'bibtex-keys) +(defvar bibtex-entry-kill-ring nil + "Ring of least recently killed entries. +At most `bibtex-entry-kill-ring-max' items are kept here.") +(defvar bibtex-entry-kill-ring-yank-pointer nil + "The tail of `bibtex-entry-kill-ring' whose car is the last item yanked.") - -;; Functions to Parse the BibTeX Entries - -(defun bibtex-cfield (name text) - ;; Create a regexp for a BibTeX field of name NAME and text TEXT. - (concat ",[ \t\n]*\\(" - name - "\\)[ \t\n]*=[ \t\n]*\\(" - text - "\\)")) -(defconst bibtex-name-in-cfield 1) -;; The regexp subexpression number of the name part in bibtex-cfield. - -(defconst bibtex-text-in-cfield 2) -;; The regexp subexpression number of the text part in bibtex-cfield. - -(defconst bibtex-field-name "[A-Za-z_-][A-Za-z0-9_-]*") -;; Regexp defining the name part of a BibTeX field. - -(defconst bibtex-field-const "[0-9A-Za-z][A-Za-z0-9:_+-]*" - "Format of a bibtex field constant.") - -(defconst bibtex-field-string - (concat - "\\(" - "{\\(\\({\\(\\({[^}]*}\\)\\|\\([^{}]\\)\\)*}\\)\\|\\([^{}]\\)\\)*}" - ;; maximal twice nested {} - "\\)\\|\\(" - "\"[^\"]*[^\\\\]\"\\|\"\"" - "\\)")) -;; Match either a string or an empty string. - -(defconst bibtex-field-string-or-const - (concat bibtex-field-const "\\|" bibtex-field-string)) -;; Match either bibtex-field-string or bibtex-field-const. - -(defconst bibtex-field-text - (concat - "\\(" bibtex-field-string-or-const "\\)" - "\\([ \t\n]+#[ \t\n]+\\(" bibtex-field-string-or-const "\\)\\)*")) -;; Regexp defining the text part of a BibTeX field: either a string, -;; or an empty string, or a constant followed by one or more # / -;; constant pairs. - -(defconst bibtex-field - (bibtex-cfield bibtex-field-name bibtex-field-text)) -;; Regexp defining the format of a BibTeX field. - -(defconst bibtex-name-in-field bibtex-name-in-cfield) -;; The regexp subexpression number of the name part in BibTeX-field. - -(defconst bibtex-text-in-field bibtex-text-in-cfield) -;; The regexp subexpression number of the text part in BibTeX-field. - -(defconst bibtex-reference-type "@[A-Za-z]+") -;; Regexp defining the type part of a BibTeX reference entry. - -(defconst bibtex-reference-key "[A-Za-z][A-Za-z0-9.:;?!`'/*@_+-]*") -;; Regexp defining the label part of a BibTeX reference entry. - -(defconst bibtex-reference-head - (concat "^\\( \\|\t\\)*\\(" - bibtex-reference-type - "\\)[ \t]*[({]\\(" - bibtex-reference-key - "\\)")) -;; Regexp defining format of the header line of a BibTeX reference -;; entry. - -(defconst bibtex-reference-maybe-empty-head - (concat bibtex-reference-head "?")) -;; Regexp defining format of the header line of a maybe empty -;; BibTeX reference entry (without reference key). - -(defconst bibtex-type-in-head 2) -;; The regexp subexpression number of the type part in -;; bibtex-reference-head. - -(defconst bibtex-key-in-head 3) -;; The regexp subexpression number of the key part in -;; bibtex-reference-head. - -(defconst bibtex-reference - (concat bibtex-reference-head - "\\([ \t\n]*" bibtex-field "\\)*" - "[ \t\n]*,?[ \t\n]*[})]")) -;; Regexp defining the format of a BibTeX reference entry. - -(defconst bibtex-type-in-reference bibtex-type-in-head) -;; The regexp subexpression number of the type part in -;; bibtex-reference. - -(defconst bibtex-key-in-reference bibtex-key-in-head) -;; The regexp subexpression number of the key part in -;; bibtex-reference. - -(defconst bibtex-string - (concat "^[ \t]*@[sS][tT][rR][iI][nN][gG][ \t\n]*[({][ \t\n]*\\(" - bibtex-reference-key - "\\)[ \t\n]*=[ \t\n]*\\(" - bibtex-field-text - "\\)[ \t\n]*[})]")) -;; Regexp defining the format of a BibTeX string entry. - -(defconst bibtex-key-in-string 1) -;; The regexp subexpression of the name part in bibtex-string. - -(defconst bibtex-text-in-string 2) -;; The regexp subexpression of the text part in bibtex-string. - -(defconst bibtex-name-alignment 2) -;; Alignment for the name part in BibTeX fields. Chosen on aesthetic -;; grounds only. - -(defconst bibtex-text-alignment (length " organization = ")) -;; Alignment for the text part in BibTeX fields. Equal to the space -;; needed for the longest name part. +(defvar bibtex-last-kill-command nil + "Type of the last kill command (either 'field or 'entry).") + +(defvar bibtex-strings + (lazy-completion-table bibtex-strings + (lambda () + (bibtex-parse-strings (bibtex-string-files-init)))) + "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 + (lambda () (bibtex-parse-keys nil t))) + "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.") + +(defvar bibtex-parse-idle-timer nil + "Stores if timer is already installed.") +(defvar bibtex-progress-lastperc nil + "Last reported percentage for the progress message.") + +(defvar bibtex-progress-lastmes nil + "Last reported progress message.") + +(defvar bibtex-progress-interval nil + "Interval for progress messages.") + +(defvar bibtex-key-history nil + "History list for reading keys.") + +(defvar bibtex-entry-type-history nil + "History list for reading entry types.") + +(defvar bibtex-field-history nil + "History list for reading field names.") + +(defvar bibtex-reformat-previous-options nil + "Last reformat options given.") + +(defvar bibtex-reformat-previous-reference-keys nil + "Last reformat reference keys option given.") + +(defconst bibtex-field-name "[^\"#%'(),={} \t\n0-9][^\"#%'(),={} \t\n]*" + "Regexp matching the name of a BibTeX field.") + +(defconst bibtex-name-part + (concat ",[ \t\n]*\\(" bibtex-field-name "\\)") + "Regexp matching the name part of a BibTeX field.") + +(defconst bibtex-reference-key "[][[:alnum:].:;?!`'/*@+|()<>&_^$-]+" + "Regexp matching the reference key part of a BibTeX entry.") + +(defconst bibtex-field-const "[][[:alnum:].:;?!`'/*@+=|<>&_^$-]+" + "Regexp matching a BibTeX field constant.") + +(defvar bibtex-entry-type + (concat "@[ \t]*\\(?:" + (regexp-opt (mapcar 'car bibtex-entry-field-alist)) "\\)") + "Regexp matching the name of a BibTeX entry.") + +(defvar bibtex-entry-head + (concat "^[ \t]*\\(" + bibtex-entry-type + "\\)[ \t]*[({][ \t\n]*\\(" + bibtex-reference-key + "\\)") + "Regexp matching the header line of a BibTeX entry (including key).") + +(defvar bibtex-entry-maybe-empty-head + (concat bibtex-entry-head "?") + "Regexp matching the header line of a BibTeX entry (possibly without key).") + +(defconst bibtex-any-entry-maybe-empty-head + (concat "^[ \t]*\\(@[ \t]*" bibtex-field-name "\\)[ \t]*[({][ \t\n]*\\(" + 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-string-type "^[ \t]*\\(@[ \t]*String\\)[ \t]*[({][ \t\n]*" + "Regexp matching the name of a BibTeX String entry.") + +(defconst bibtex-string-maybe-empty-head + (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]*[({][ \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) "\\)" + (substring bibtex-comment-start 1) "\\>") + 1 '(11)))) + +(defvar bibtex-font-lock-keywords + ;; entry type and reference key + `((,bibtex-any-entry-maybe-empty-head + (,bibtex-type-in-head font-lock-function-name-face) + (,bibtex-key-in-head font-lock-constant-face nil t)) + ;; optional field names (treated as comments) + (,(concat "^[ \t]*\\(OPT" bibtex-field-name "\\)[ \t]*=") + 1 font-lock-comment-face) + ;; field names + (,(concat "^[ \t]*\\(" bibtex-field-name "\\)[ \t]*=") + 1 font-lock-variable-name-face) + ;; url + (bibtex-font-lock-url) (bibtex-font-lock-crossref)) + "*Default expressions to highlight in BibTeX mode.") + +(defvar bibtex-font-lock-url-regexp + ;; Assume that field names begin at the beginning of a line. + (concat "^[ \t]*" + (regexp-opt (delete-dups (mapcar 'caar bibtex-generate-url-list)) t) + "[ \t]*=[ \t]*") + "Regexp for `bibtex-font-lock-url'.") + +(defvar bibtex-string-empty-key nil + "If non-nil, `bibtex-parse-string' accepts empty key.") + +(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'.") +;; Support for hideshow minor mode +(defun bibtex-hs-forward-sexp (arg) + "Replacement for `forward-sexp' to be used by `hs-minor-mode'. +ARG is ignored." + (if (looking-at "@\\S(*\\s(") + (goto-char (1- (match-end 0)))) + (forward-sexp 1)) + +(add-to-list + 'hs-special-modes-alist + '(bibtex-mode "@\\S(*\\s(" "\\s)" nil bibtex-hs-forward-sexp nil)) + + +(defun bibtex-parse-association (parse-lhs parse-rhs) + "Parse a string of the format . +The functions PARSE-LHS and PARSE-RHS are used to parse the corresponding +substrings. These functions are expected to return nil if parsing is not +successful. If the returned values of both functions are non-nil, +return a cons pair of these values. Do not move point." + (save-match-data + (save-excursion + (let ((left (funcall parse-lhs)) + right) + (if (and left + (looking-at "[ \t\n]*=[ \t\n]*") + (goto-char (match-end 0)) + (setq right (funcall parse-rhs))) + (cons left right)))))) + +(defun bibtex-parse-field-name () + "Parse the name part of a BibTeX field. +If the field name is found, return a triple consisting of the position of the +very first character of the match, the actual starting position of the name +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 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 + "\\)[ \t\n]*="))) + (skip-chars-backward " \t\n") + ;; It can be confusing if non-editing commands try to + ;; modify the buffer. + (if buffer-read-only + (error "Comma missing at buffer position %s" (point))) + (insert ",") + (forward-char -1) + ;; Now try again. + (bibtex-parse-field-name)))) + +(defconst bibtex-braced-string-syntax-table + (let ((st (make-syntax-table))) + (modify-syntax-entry ?\{ "(}" st) + (modify-syntax-entry ?\} "){" st) + (modify-syntax-entry ?\[ "." st) + (modify-syntax-entry ?\] "." st) + (modify-syntax-entry ?\( "." st) + (modify-syntax-entry ?\) "." st) + (modify-syntax-entry ?\\ "." st) + (modify-syntax-entry ?\" "." st) + st) + "Syntax-table to parse matched braces.") + +(defconst bibtex-quoted-string-syntax-table + (let ((st (make-syntax-table))) + (modify-syntax-entry ?\\ "\\" st) + (modify-syntax-entry ?\" "\"" st) + st) + "Syntax-table to parse matched quotes.") + +(defun bibtex-parse-field-string () + "Parse a BibTeX field string enclosed by braces or quotes. +If a syntactically correct string is found, a pair containing the start and +end position of the field string is returned, nil otherwise. +Do not move point." + (let ((end-point + (or (and (eq (following-char) ?\") + (save-excursion + (with-syntax-table bibtex-quoted-string-syntax-table + (forward-sexp 1)) + (point))) + (and (eq (following-char) ?\{) + (save-excursion + (with-syntax-table bibtex-braced-string-syntax-table + (forward-sexp 1)) + (point)))))) + (if end-point + (cons (point) end-point)))) + +(defun bibtex-parse-field-text () + "Parse the text part of a BibTeX field. +The text part is either a string, or an empty string, or a constant followed +by one or more <# (string|constant)> pairs. If a syntactically correct text +is found, a pair containing the start and end position of the text is +returned, nil otherwise. Move point to end of field text." + (let ((starting-point (point)) + end-point failure boundaries) + (while (not (or end-point failure)) + (cond ((looking-at bibtex-field-const) + (goto-char (match-end 0))) + ((setq boundaries (bibtex-parse-field-string)) + (goto-char (cdr boundaries))) + ((setq failure t))) + (if (looking-at "[ \t\n]*#[ \t\n]*") + (goto-char (match-end 0)) + (setq end-point (point)))) + (skip-chars-forward " \t\n") + (if (and (not failure) + end-point) + (list starting-point end-point (point))))) + +(defun bibtex-parse-field () + "Parse the BibTeX field beginning at the position of point. +If a syntactically correct field is found, return a cons pair containing +the boundaries of the name and text parts of the field. Do not move point." + (bibtex-parse-association 'bibtex-parse-field-name + 'bibtex-parse-field-text)) + +(defsubst bibtex-start-of-field (bounds) + (nth 0 (car bounds))) +(defsubst bibtex-start-of-name-in-field (bounds) + (nth 1 (car bounds))) +(defsubst bibtex-end-of-name-in-field (bounds) + (nth 2 (car bounds))) +(defsubst bibtex-start-of-text-in-field (bounds) + (nth 1 bounds)) +(defsubst bibtex-end-of-text-in-field (bounds) + (nth 2 bounds)) +(defsubst bibtex-end-of-field (bounds) + (nth 3 bounds)) + +(defun bibtex-search-forward-field (name &optional bound) + "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. 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 (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 t the search is +limited by the beginning of the current entry. Do not move point." + (save-match-data + (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 optional arg REMOVE-OPT-ALT is non-nil remove \"OPT\" and \"ALT\"." + (let ((name (buffer-substring-no-properties + (bibtex-start-of-name-in-field bounds) + (bibtex-end-of-name-in-field bounds)))) + (if (and remove-opt-alt + (string-match "\\`\\(OPT\\|ALT\\)" name)) + (substring name 3) + name))) + +(defun bibtex-text-in-field-bounds (bounds &optional content) + "Get text in BibTeX field defined via BOUNDS. +If optional arg CONTENT is non-nil extract content of field +by removing field delimiters and concatenating the resulting string. +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) + (while (< (setq opoint (point)) epoint) + (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)) + (apply 'concat (nreverse content)))) + (buffer-substring-no-properties (bibtex-start-of-text-in-field bounds) + (bibtex-end-of-text-in-field bounds)))) + +(defun bibtex-text-in-field (field &optional follow-crossref) + "Get content of field FIELD of current BibTeX entry. +Return nil if not found. +If optional arg FOLLOW-CROSSREF is non-nil, follow crossref." + (save-excursion + (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. + (bibtex-text-in-field field)))))))) + +(defun bibtex-parse-string-prefix () + "Parse the prefix part of a BibTeX string entry, including reference key. +If the string prefix is found, return a triple consisting of the position of +the very first character of the match, the actual starting position of the +reference key and the end position of the match. +If `bibtex-string-empty-key' is non-nil accept empty string key." + (let ((case-fold-search t)) + (if (looking-at bibtex-string-type) + (let ((start (point))) + (goto-char (match-end 0)) + (cond ((looking-at bibtex-reference-key) + (goto-char (match-end 0)) + (list start + (match-beginning 0) + (match-end 0))) + ((and bibtex-string-empty-key + (looking-at "=")) + (skip-chars-backward " \t\n") + (list start (point) (point)))))))) + +(defun bibtex-parse-string-postfix () + "Parse the postfix part of a BibTeX string entry, including the text. +If the string postfix is found, return a triple consisting of the position of +the actual starting and ending position of the text and the very last +character of the string entry. Move point past BibTeX string entry." + (let* ((case-fold-search t) + (bounds (bibtex-parse-field-text))) + (when bounds + (goto-char (nth 1 bounds)) + (when (looking-at "[ \t\n]*[})]") + (goto-char (match-end 0)) + (list (car bounds) + (nth 1 bounds) + (match-end 0)))))) + +(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. +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 (&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. +If EMPTY-KEY is non-nil, key may be empty. Do not move point." + (save-excursion + (save-match-data + (let ((case-fold-search t) bounds) + (while (and (not bounds) + (search-forward-regexp bibtex-string-type nil t)) + (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" + (buffer-substring-no-properties (nth 1 (car bounds)) + (nth 2 (car bounds)))) + +(defun bibtex-text-in-string (bounds &optional content) + "Get text in BibTeX string field defined via BOUNDS. +If optional arg CONTENT is non-nil extract content +by removing field delimiters and concatenating the resulting string. +If `bibtex-expand-strings' is non-nil, also expand BibTeX strings." + (bibtex-text-in-field-bounds bounds content)) + +(defsubst bibtex-start-of-text-in-string (bounds) + (nth 0 (cdr bounds))) +(defsubst bibtex-end-of-text-in-string (bounds) + (nth 1 (cdr bounds))) +(defsubst bibtex-end-of-string (bounds) + (nth 2 (cdr bounds))) + +(defsubst bibtex-type-in-head () + "Extract BibTeX type in head." + ;; ignore @ + (buffer-substring-no-properties (1+ (match-beginning bibtex-type-in-head)) + (match-end bibtex-type-in-head))) + +(defsubst bibtex-key-in-head (&optional empty) + "Extract BibTeX key in head. Return optional arg EMPTY if key is empty." + (or (match-string-no-properties bibtex-key-in-head) + empty)) + +(defun bibtex-parse-preamble () + "Parse BibTeX preamble. +Point must be at beginning of preamble. Do not move point." + (let ((case-fold-search 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 -(defun assoc-ignore-case (string alist) - ;; Return non-nil if STRING is `equal' to the car of an element of - ;; LIST. Comparison is done with case ignored. The value is actually - ;; the element of LIST whose car is `equal' to STRING. - (or (assoc string alist) - (while (and alist - (not (string-equal - (downcase string) - (downcase (car (car alist)))))) - (setq alist (cdr alist))) - (car alist))) - -(defun member-of-regexp (string list) - ;; Return non-nil if STRING is exactly matched by an element of - ;; LIST. This function is influenced by the actual value of - ;; `case-fold-search'. The value is actually the tail of LIST whose - ;; car matches STRING. - (while - (and - list - (not - (string-match - (concat "^" (car list) "$") - string))) - (setq list (cdr list))) - list) - -(defun assoc-of-regexp (string alist) - ;; Return non-nil if STRING is exactly matched by the car of an - ;; element of LIST. This function is influenced by the actual value - ;; of `case-fold-search'. The value is actually the element of LIST - ;; whose car matches STRING. - (while - (and - alist - (not - (string-match - (concat "^" (car (car alist)) "$") - string))) - (setq alist (cdr alist))) - (car alist)) - -(defun skip-whitespace-and-comments () - (let ((md (match-data))) - (unwind-protect - (while (cond ((looking-at "\\s>+\\|\\s +") - ;; was whitespace - ;; NOTE: also checked end-comment. In latex and - ;; lisp modes, newline is an end comment, but it - ;; should also be a whitespace char. - (goto-char (match-end 0))) - ;; If looking at beginning of comment, skip to end. - ((looking-at "\\s<") - (re-search-forward "\\s>")))) - (store-match-data md)))) - -(defun map-bibtex-entries (fun) - ;; Call FUN for each BibTeX entry starting with the current. Do this - ;; to the end of the file. FUN is called with one argument, the key - ;; of the entry, and with point inside the entry. If - ;; bibtex-sort-ignore-string-entries is non-nil, FUN will not be called - ;; for @string entries. - (bibtex-beginning-of-entry) - (while (re-search-forward bibtex-reference-head nil t) - (if (and bibtex-sort-ignore-string-entries - (string-equal "@string" - (downcase (buffer-substring-no-properties - (match-beginning bibtex-type-in-head) - (match-end bibtex-type-in-head))))) - nil - (funcall fun (buffer-substring-no-properties - (match-beginning bibtex-key-in-head) - (match-end bibtex-key-in-head)))))) - -(defun bibtex-flash-head () - ;; Flash at BibTeX reference head before point, if exists. - (let ((flash)) - (cond ((re-search-backward bibtex-reference-head (point-min) t) - (goto-char (match-beginning bibtex-type-in-head)) - (setq flash (match-end bibtex-key-in-reference))) - (t - (end-of-line) - (skip-chars-backward " \t") - (setq flash (point)) - (beginning-of-line) - (skip-chars-forward " \t"))) - (if (pos-visible-in-window-p (point)) - (sit-for 1) - (message "From: %s" - (buffer-substring (point) flash))))) +(defsubst bibtex-string= (str1 str2) + "Return t if STR1 and STR2 are equal, ignoring case." + (eq t (compare-strings str1 0 nil str2 0 nil t))) + +(defun bibtex-delete-whitespace () + "Delete all whitespace starting at point." + (if (looking-at "[ \t\n]+") + (delete-region (point) (match-end 0)))) + +(defun bibtex-current-line () + "Compute line number of point regardless whether the buffer is narrowed." + (+ (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. +With optional argument BACKWARD non-nil, move backward to +beginning of previous valid one. A valid entry is a syntactical correct one +with type contained in `bibtex-entry-field-alist' or, if +`bibtex-sort-ignore-string-entries' is nil, a syntactical correct string +entry. Return buffer position of beginning and end of entry if a valid +entry is found, nil otherwise." + (interactive "P") + (let ((case-fold-search t) + found bounds) + (beginning-of-line) + ;; Loop till we look at a valid entry. + (while (not (or found (if backward (bobp) (eobp)))) + (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 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 + (goto-char (point-min)) + (while (setq found (bibtex-skip-to-valid-entry)) + (looking-at bibtex-any-entry-maybe-empty-head) + (funcall fun (bibtex-key-in-head "") (car found) (cdr found)) + (goto-char (cdr found)))))) + +(defun bibtex-progress-message (&optional flag interval) + "Echo a message about progress of current buffer. +If FLAG is a string, the message is initialized (in this case a +value for INTERVAL may be given as well (if not this is set to 5)). +If FLAG is `done', the message is deinitialized. +If FLAG is nil, a message is echoed if point was incremented at least +`bibtex-progress-interval' percent since last message was echoed." + (cond ((stringp flag) + (setq bibtex-progress-lastmes flag + bibtex-progress-interval (or interval 5) + bibtex-progress-lastperc 0)) + ((eq flag 'done) + (message "%s (done)" bibtex-progress-lastmes) + (setq bibtex-progress-lastmes nil)) + (t + (let* ((size (- (point-max) (point-min))) + (perc (if (= size 0) + 100 + (/ (* 100 (- (point) (point-min))) size)))) + (when (>= perc (+ bibtex-progress-lastperc + bibtex-progress-interval)) + (setq bibtex-progress-lastperc perc) + (message "%s (%d%%)" bibtex-progress-lastmes perc)))))) + +(defun bibtex-field-left-delimiter () + "Return a string dependent on `bibtex-field-delimiters'." + (if (eq bibtex-field-delimiters 'braces) + "{" + "\"")) + +(defun bibtex-field-right-delimiter () + "Return a string dependent on `bibtex-field-delimiters'." + (if (eq bibtex-field-delimiters 'braces) + "}" + "\"")) + +(defun bibtex-entry-left-delimiter () + "Return a string dependent on `bibtex-entry-delimiters'." + (if (eq bibtex-entry-delimiters 'braces) + "{" + "(")) + +(defun bibtex-entry-right-delimiter () + "Return a string dependent on `bibtex-entry-delimiters'." + (if (eq bibtex-entry-delimiters 'braces) + "}" + ")")) + +(defun bibtex-flash-head (prompt) + "Flash at BibTeX entry head before point, if exists." + (let ((case-fold-search t) + (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)) + (if (pos-visible-in-window-p (point)) + (sit-for 1) + (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." + (if (consp field) + (bibtex-make-field (cons (concat "OPT" (car field)) (cdr field))) + (bibtex-make-field (concat "OPT" field)))) (defun bibtex-move-outside-of-entry () - ;; Make sure we are outside of a BibTeX entry. - (cond ((or - (= (point) (point-max)) - (= (point) (point-min)) - (looking-at "[ \n]*@") - ) - t) - (t - (backward-paragraph) - (forward-paragraph))) - (re-search-forward "[ \t\n]*" (point-max) t)) - -(defun beginning-of-first-bibtex-entry () - ;; Go to the beginning of the first BibTeX entry in buffer. + "Make sure point is outside of a BibTeX entry." + (let ((orig-point (point))) + (bibtex-end-of-entry) + (when (< (point) orig-point) + ;; We moved backward, so we weren't inside an entry to begin with. + ;; Leave point at the beginning of a line, and preferably + ;; at the beginning of a paragraph. + (goto-char orig-point) + (beginning-of-line 1) + (unless (= ?\n (char-before (1- (point)))) + (re-search-forward "^[ \t]*[@\n]" nil 'move) + (backward-char 1))) + (skip-chars-forward " \t\n"))) + +(defun bibtex-beginning-of-first-entry () + "Go to beginning of line of first BibTeX entry in buffer. +If `bibtex-sort-ignore-string-entries' is non-nil, @String entries +are ignored. Return point" (goto-char (point-min)) - (cond - ((re-search-forward "^@" nil 'move) - (beginning-of-line)) - ((and (bobp) (eobp)) - nil) - (t - (message "Warning: No BibTeX entries found!")))) - -(defun bibtex-inside-field () - ;; Try to avoid point being at end of a BibTeX field. - (end-of-line) - (skip-chars-backward " \t") - (cond ((= (preceding-char) ?,) - (forward-char -2))) - (cond ((or - (= (preceding-char) ?}) - (= (preceding-char) ?\")) - (forward-char -1)))) - -(defun bibtex-enclosing-field () - ;; Search for BibTeX field enclosing point. Point moves to end of - ;; field; also, use match-beginning and match-end to parse the field. - ;; sct@dcs.edinburgh.ac.uk - (let ((old-point (point))) - (condition-case errname - (bibtex-enclosing-regexp bibtex-field) - (search-failed - (goto-char old-point) - (error "Can't find enclosing BibTeX field."))))) - -(defun bibtex-enclosing-reference () - ;; Search for BibTeX reference enclosing point. Point moves to - ;; beginning of reference. Beginning/end of reference is given by - ;; (match-beginning/match-end 0). - (let ((old-point (point))) - (if (not - (re-search-backward bibtex-reference-head (point-min) t)) - (progn - (error "Can't find enclosing BibTeX reference.") - (goto-char old-point))) - (goto-char (match-beginning bibtex-type-in-head)) - (let ((pnt (point))) - (if (not - (re-search-forward bibtex-reference (point-max) t)) - (progn - (error "Can't find enclosing BibTeX reference.") - (goto-char old-point)) - (goto-char pnt))))) - -(defun bibtex-enclosing-reference-maybe-empty-head () - ;; Search for BibTeX reference enclosing point. Point moves to - ;; beginning of reference. Beginning/end of reference is given by - ;; (match-beginning/match-end 0). - (let ((old-point (point))) - (if (not - (re-search-backward - bibtex-reference-maybe-empty-head (point-min) t)) + (bibtex-skip-to-valid-entry) + (point)) + +(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." + (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 - (error "Can't find enclosing BibTeX reference.") - (goto-char old-point))) - (goto-char (match-beginning bibtex-type-in-head)) - (let ((pnt (point))) - (if (not - (re-search-forward - (concat - bibtex-reference-maybe-empty-head - "\\([ \t\n]*" bibtex-field "\\)*" - "[ \t\n]*,?[ \t\n]*[})]") - (point-max) t)) - (progn - (error "Can't find enclosing BibTeX reference.") - (goto-char old-point)) - (goto-char pnt))))) - -(defun bibtex-enclosing-regexp (regexp) - ;; Search for REGEXP enclosing point. Point moves to end of - ;; REGEXP. See also match-beginning and match-end. If an enclosing - ;; REGEXP is not found, signals search-failed; point is left in an - ;; undefined location. - ;; Doesn't something like this exist already? - ; compute reasonable limits for the loop - (let* ((initial (point)) - (right (if (re-search-forward regexp (point-max) t) - (match-end 0) - (point-max))) - (left - (progn - (goto-char initial) - (if (re-search-backward regexp (point-min) t) - (match-beginning 0) - (point-min))))) - ; within the prescribed limits, loop until a match is found - (goto-char left) - (re-search-forward regexp right nil 1) - (if (> (match-beginning 0) initial) - (signal 'search-failed (list regexp))) - (while (<= (match-end 0) initial) - (re-search-forward regexp right nil 1) - (if (> (match-beginning 0) initial) - (signal 'search-failed (list regexp)))) - )) - -(defun bibtex-autokey-change (string change-list) - ;; Returns a string where some regexps are changed according to - ;; change-list. Every item of change-list is an (old-regexp - ;; new-string) pair. - (let ((return-string string) - case-fold-search - (index 0) - (len (length change-list)) - change-item) - (while (< index len) - (setq change-item (elt change-list index)) - (while (string-match (car change-item) return-string) - (setq - return-string - (concat (substring return-string 0 (match-beginning 0)) - (elt change-item 1) - (substring return-string (match-end 0))))) - (setq index (1+ index))) - return-string)) + ;; insert past the current field + (goto-char (bibtex-end-of-field (bibtex-enclosing-field comma))) + (set-mark (point)) + (message "Mark set") + (bibtex-make-field (funcall fun 'bibtex-field-kill-ring-yank-pointer + bibtex-field-kill-ring) t)) + ;; insert past the current entry + (bibtex-skip-to-valid-entry) + (set-mark (point)) + (message "Mark set") + (insert (funcall fun 'bibtex-entry-kill-ring-yank-pointer + bibtex-entry-kill-ring))))) + +(defun bibtex-format-entry () + "Helper function for `bibtex-clean-entry'. +Formats current entry according to variable `bibtex-entry-format'." + (save-excursion + (save-restriction + (bibtex-narrow-to-entry) + (let ((case-fold-search t) + (format (if (eq bibtex-entry-format t) + '(realign opts-or-alts required-fields + numerical-fields + last-comma page-dashes delimiters + unify-case inherit-booktitle) + bibtex-entry-format)) + crossref-key bounds alternatives-there non-empty-alternative + entry-list req-field-list field-list) + + ;; identify entry type + (goto-char (point-min)) + (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 + beg-type end-type) + bibtex-entry-field-alist + t)) + + ;; unify case of entry name + (when (memq 'unify-case format) + (delete-region beg-type end-type) + (insert (car entry-list))) + + ;; update left entry delimiter + (when (memq 'delimiters format) + (goto-char end-type) + (skip-chars-forward " \t\n") + (delete-char 1) + (insert (bibtex-entry-left-delimiter)))) + + ;; determine if entry has crossref field and if at least + ;; one alternative is non-empty + (goto-char (point-min)) + (let* ((fields-alist (bibtex-parse-entry t)) + (field (assoc-string "crossref" fields-alist t))) + (setq crossref-key (and field + (not (equal "" (cdr field))) + (cdr field)) + req-field-list (if crossref-key + (nth 0 (nth 2 entry-list)) ; crossref part + (nth 0 (nth 1 entry-list)))) ; required part + + (dolist (rfield req-field-list) + (when (nth 3 rfield) ; we should have an alternative + (setq alternatives-there t + field (assoc-string (car rfield) fields-alist t)) + (if (and field + (not (equal "" (cdr field)))) + (cond ((not non-empty-alternative) + (setq non-empty-alternative t)) + ((memq 'required-fields format) + (error "More than one non-empty alternative"))))))) + + (if (and alternatives-there + (not non-empty-alternative) + (memq 'required-fields format)) + (error "All alternatives are empty")) + + ;; process all fields + (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))) + (end-name (copy-marker (bibtex-end-of-name-in-field bounds))) + (beg-text (copy-marker (bibtex-start-of-text-in-field bounds))) + (end-text (copy-marker (bibtex-end-of-text-in-field bounds) t)) + (opt-alt (string-match "OPT\\|ALT" + (buffer-substring-no-properties + beg-name (+ beg-name 3)))) + (field-name (buffer-substring-no-properties + (if opt-alt (+ beg-name 3) beg-name) end-name)) + (empty-field (equal "" (bibtex-text-in-field-bounds bounds t))) + deleted) + + ;; We have more elegant high-level functions for several + ;; 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. + + (if (memq 'opts-or-alts format) + (cond ((and empty-field + (or opt-alt + (let ((field (assoc-string + field-name req-field-list t))) + (or (not field) ; OPT field + (nth 3 field))))) ; ALT field + ;; Either it is an empty ALT field. Then we have checked + ;; already that we have one non-empty alternative. Or it + ;; is an empty OPT field that we do not miss anyway. + ;; So we can safely delete this field. + (delete-region beg-field end-field) + (setq deleted t)) + ;; otherwise: not empty, delete "OPT" or "ALT" + (opt-alt + (goto-char beg-name) + (delete-char 3)))) + + (unless deleted + (push field-name field-list) + + ;; remove delimiters from purely numerical fields + (when (and (memq 'numerical-fields format) + (progn (goto-char beg-text) + (looking-at "\\(\"[0-9]+\"\\)\\|\\({[0-9]+}\\)"))) + (goto-char end-text) + (delete-char -1) + (goto-char beg-text) + (delete-char 1)) + + ;; update delimiters + (when (memq 'delimiters format) + (goto-char beg-text) + (when (looking-at "[{\"]") + (delete-char 1) + (insert (bibtex-field-left-delimiter))) + (goto-char (1- (marker-position end-text))) + (when (looking-at "[}\"]") + (delete-char 1) + (insert (bibtex-field-right-delimiter)))) + + ;; update page dashes + (if (and (memq 'page-dashes format) + (bibtex-string= field-name "pages") + (progn (goto-char beg-text) + (looking-at + "\\([\"{][0-9]+\\)[ \t\n]*--?[ \t\n]*\\([0-9]+[\"}]\\)"))) + (replace-match "\\1-\\2")) + + ;; use book title of crossref'd entry + (if (and (memq 'inherit-booktitle format) + empty-field + (bibtex-string= field-name "booktitle") + crossref-key) + (let ((title (save-excursion + (save-restriction + (widen) + (if (bibtex-find-entry crossref-key t) + (bibtex-text-in-field "title")))))) + (when title + (setq empty-field nil) + (goto-char (1+ beg-text)) + (insert title)))) + + ;; Use booktitle to set a missing title. + (if (and empty-field + (bibtex-string= field-name "title")) + (let ((booktitle (bibtex-text-in-field "booktitle"))) + (when booktitle + (setq empty-field nil) + (goto-char (1+ beg-text)) + (insert booktitle)))) + + ;; if empty field, complain + (if (and empty-field + (memq 'required-fields format) + (assoc-string field-name req-field-list t)) + (error "Mandatory field `%s' is empty" field-name)) + + ;; unify case of field name + (if (memq 'unify-case format) + (let ((fname (car (assoc-string + field-name + (append (nth 0 (nth 1 entry-list)) + (nth 1 (nth 1 entry-list)) + bibtex-user-optional-fields) + t)))) + (if fname + (progn + (delete-region beg-name end-name) + (goto-char beg-name) + (insert fname)) + ;; there are no rules we could follow + (downcase-region beg-name end-name)))) + + ;; update point + (goto-char end-field)))) + + ;; check whether all required fields are present + (if (memq 'required-fields format) + (let ((found 0) altlist) + (dolist (fname req-field-list) + (if (nth 3 fname) + (push (car fname) altlist)) + (unless (or (member (car fname) field-list) + (nth 3 fname)) + (error "Mandatory field `%s' is missing" (car fname)))) + (when altlist + (dolist (fname altlist) + (if (member fname field-list) + (setq found (1+ found)))) + (cond ((= found 0) + (error "Alternative mandatory field `%s' is missing" + altlist)) + ((> found 1) + (error "Alternative fields `%s' are defined %s times" + altlist found)))))) + + ;; update comma after last field + (if (memq 'last-comma format) + (cond ((and bibtex-comma-after-last-field + (not (looking-at ","))) + (insert ",")) + ((and (not bibtex-comma-after-last-field) + (looking-at ",")) + (delete-char 1)))) + + ;; update right entry delimiter + (if (looking-at ",") + (forward-char)) + (when (memq 'delimiters format) + (skip-chars-forward " \t\n") + (delete-char 1) + (insert (bibtex-entry-right-delimiter))) + + ;; fill entry + (if (memq 'realign format) + (bibtex-fill-entry)))))) + (defun bibtex-autokey-abbrev (string len) - ;; Returns an abbreviation of string with at least len - ;; characters. String is aborted only after a consonant or at the - ;; word end. If len is not a number, string is returned unchanged. - (let* ((string-length (length string)) - (len (if (numberp len) - (min len string-length) - len)) - (return-string (if (numberp len) - (substring string 0 len))) - (index len) - (vowels '(?a ?e ?i ?o ?u ?A ?E ?I ?O ?U))) - (if (numberp len) - (progn - (while (and - (< index string-length) - (member (elt return-string - (1- (length return-string))) - vowels)) - (setq return-string (concat return-string - (substring - string index (1+ index))) - index (1+ index))) - return-string) - string))) + "Return an abbreviation of STRING with at least LEN characters. +If LEN is positive the abbreviation is terminated only after a consonant +or at the word end. If LEN is negative the abbreviation is strictly +enforced using abs (LEN) characters. If LEN is not a number, STRING +is returned unchanged." + (cond ((or (not (numberp len)) + (<= (length string) (abs len))) + string) + ((equal len 0) + "") + ((< len 0) + (substring string 0 (abs len))) + (t (let* ((case-fold-search t) + (abort-char (string-match "[^aeiou]" string (1- len)))) + (if abort-char + (substring string 0 (1+ abort-char)) + string))))) + +(defun bibtex-autokey-get-field (field &optional change-list) + "Get content of BibTeX field FIELD. Return empty string if not found. +Optional arg CHANGE-LIST is a list of substitution patterns that is +applied to the content of FIELD. It is an alist with pairs +\(OLD-REGEXP . NEW-STRING\)." + (let* ((bibtex-expand-strings bibtex-autokey-expand-strings) + (content (bibtex-text-in-field field bibtex-autokey-use-crossref)) + case-fold-search) + (unless content (setq content "")) + (dolist (pattern change-list content) + (setq content (replace-regexp-in-string (car pattern) + (cdr pattern) + content t))))) + +(defun bibtex-autokey-get-names () + "Get contents of the name field of the current entry. +Do some modifications based on `bibtex-autokey-name-change-strings'. +Return the names as a concatenated string obeying `bibtex-autokey-names' +and `bibtex-autokey-names-stretch'." + (let ((names (bibtex-autokey-get-field "author\\|editor" + bibtex-autokey-name-change-strings))) + ;; Some entries do not have a name field. + (if (string= "" names) + names + (let* ((case-fold-search t) + (name-list (mapcar 'bibtex-autokey-demangle-name + (split-string names "[ \t\n]+and[ \t\n]+"))) + additional-names) + (unless (or (not (numberp bibtex-autokey-names)) + (<= (length name-list) + (+ bibtex-autokey-names + bibtex-autokey-names-stretch))) + ;; Take bibtex-autokey-names elements from beginning of name-list + (setq name-list (nreverse (nthcdr (- (length name-list) + bibtex-autokey-names) + (nreverse name-list))) + additional-names bibtex-autokey-additional-names)) + (concat (mapconcat 'identity name-list + bibtex-autokey-name-separator) + additional-names))))) + +(defun bibtex-autokey-demangle-name (fullname) + "Get the last part from a well-formed FULLNAME and perform abbreviations." + (let* (case-fold-search + (name (cond ((string-match "\\([[:upper:]][^, ]*\\)[^,]*," fullname) + ;; Name is of the form "von Last, First" or + ;; "von Last, Jr, First" + ;; --> Take the first capital part before the comma + (match-string 1 fullname)) + ((string-match "\\([^, ]*\\)," fullname) + ;; Strange name: we have a comma, but nothing capital + ;; So we accept even lowercase names + (match-string 1 fullname)) + ((string-match "\\(\\<[[:lower:]][^ ]* +\\)+\\([[:upper:]][^ ]*\\)" + fullname) + ;; name is of the form "First von Last", "von Last", + ;; "First von von Last", or "d'Last" + ;; --> take the first capital part after the "von" parts + (match-string 2 fullname)) + ((string-match "\\([^ ]+\\) *\\'" fullname) + ;; name is of the form "First Middle Last" or "Last" + ;; --> take the last token + (match-string 1 fullname)) + (t (error "Name `%s' is incorrectly formed" fullname))))) + (funcall bibtex-autokey-name-case-convert-function + (bibtex-autokey-abbrev name bibtex-autokey-name-length)))) + +(defun bibtex-autokey-get-year () + "Return year field contents as a string obeying `bibtex-autokey-year-length'." + (let ((yearfield (bibtex-autokey-get-field "year"))) + (substring yearfield (max 0 (- (length yearfield) + bibtex-autokey-year-length))))) + +(defun bibtex-autokey-get-title () + "Get title field contents up to a terminator. +Return the result as a string" + (let ((case-fold-search t) + (titlestring + (bibtex-autokey-get-field "title" + bibtex-autokey-titleword-change-strings))) + ;; ignore everything past a terminator + (if (string-match bibtex-autokey-title-terminators titlestring) + (setq titlestring (substring titlestring 0 (match-beginning 0)))) + ;; gather words from titlestring into a list. Ignore + ;; specific words and use only a specific amount of words. + (let ((counter 0) + titlewords titlewords-extra word) + (while (and (or (not (numberp bibtex-autokey-titlewords)) + (< counter (+ bibtex-autokey-titlewords + bibtex-autokey-titlewords-stretch))) + (string-match "\\b\\w+" titlestring)) + (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 + (unless (let ((lst bibtex-autokey-titleword-ignore)) + (while (and lst + (not (string-match (concat "\\`\\(?:" (car lst) + "\\)\\'") word))) + (setq lst (cdr lst))) + lst) + (setq counter (1+ counter)) + (if (or (not (numberp bibtex-autokey-titlewords)) + (<= counter bibtex-autokey-titlewords)) + (push word titlewords) + (push word titlewords-extra)))) + ;; 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. + (unless (string-match "\\b\\w+" titlestring) + (setq titlewords (append titlewords-extra titlewords))) + (mapconcat 'bibtex-autokey-demangle-title (nreverse titlewords) + bibtex-autokey-titleword-separator)))) + +(defun bibtex-autokey-demangle-title (titleword) + "Do some abbreviations on TITLEWORD. +The rules are defined in `bibtex-autokey-titleword-abbrevs' +and `bibtex-autokey-titleword-length'." + (let ((case-fold-search t) + (alist bibtex-autokey-titleword-abbrevs)) + (while (and alist + (not (string-match (concat "\\`\\(?:" (caar alist) "\\)\\'") + titleword))) + (setq alist (cdr alist))) + (if alist + (cdar alist) + (funcall bibtex-autokey-titleword-case-convert-function + (bibtex-autokey-abbrev titleword bibtex-autokey-titleword-length))))) (defun bibtex-generate-autokey () - "Generates automatically a key from the author/editor and the title field. -The generation algorithm works as follows: - 1. If there is a non-empty author (preferred) or editor field, - use it for the name part of the key. - 2. Change any substring found in `bibtex-autokey-name-change-strings' - to the corresponding new one (see documentation of this variable - for further detail). - 3. For every of the first `bibtex-autokey-names' names in the - \"name\" field, determine the last name. - 4. From every last name, take at least `bibtex-autokey-name-length' - characters (abort only after a consonant or at a word end). - 5. Build the name part of the key by concatenating all abbreviated last - names with the string `bibtex-autokey-name-separator' between - any two. - 6. Build the year part of the key by truncating the contents of the - \"year\" field to the rightmost `bibtex-autokey-year-length' - digits (useful values are 2 and 4). - 7. For the title part of the key change the contents of the \"title\" - field of the reference according to - `bibtex-autokey-titleword-change-strings' to the corresponding - new one (see documentation of this variable for further detail). - 8. Abbreviate the result to the string up to (but not including) the - first occurrence of a regexp matched by the items of - `bibtex-autokey-title-terminators' and delete the first - word if it appears in `bibtex-autokey-titleword-first-ignore'. - Build the title part of the key by using at least the first - `bibtex-autokey-titlewords' capitalized words from this - abbreviated title. If the abbreviated title ends after maximal - `bibtex-autokey-titlewords' + `bibtex-autokey-titlewords-stretch' - capitalized words, all capitalized words from the abbreviated title - are used. - 9. For every used title word that appears in - `bibtex-autokey-titleword-abbrevs' use the corresponding abbreviation - (see documentation of this variable for further detail). - 10. From every title word not generated by an abbreviation, take at - least `bibtex-autokey-titleword-length' characters (abort only after - a consonant or at a word end). - 11. Build the title part of the key by concatenating all abbreviated - title words with the string `bibtex-autokey-titleword-separator' - between any two. - 12. At least, to get the key, concatenate the name part, the year part - and the title part with `bibtex-autokey-name-year-separator' - between the name and the year if both are non-empty and - `bibtex-autokey-year-title-separator' between the year and - the title if both are non-empty." + "Generate automatically a key for a BibTeX entry. +Use the author/editor, the year and the title field. +The algorithm works as follows. + +The name part: + 1. Use the author or editor field to generate the name part of the key. + Expand BibTeX strings if `bibtex-autokey-expand-strings' is non-nil. + 2. Change the content of the name field according to + `bibtex-autokey-name-change-strings' (see there for further detail). + 3. Use the first `bibtex-autokey-names' names in the name field. If there + are up to `bibtex-autokey-names' + `bibtex-autokey-names-stretch' names, + use all names. + 4. Use only the last names to form the name part. From these last names, + 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-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 + part, append the string `bibtex-autokey-additional-names'. + +The year part: + 1. Build the year part of the key by truncating the content of the year + field to the rightmost `bibtex-autokey-year-length' digits (useful + values are 2 and 4). + 2. If the year field (or any other field required to generate the key) + is absent, but the entry has a valid crossref field and + `bibtex-autokey-use-crossref' is non-nil, use the field of the + crossreferenced entry instead. + +The title part + 1. Change the content of the title field according to + `bibtex-autokey-titleword-change-strings' (see there for further detail). + 2. Truncate the title before the first match of + `bibtex-autokey-title-terminators' and delete those words which appear + in `bibtex-autokey-titleword-ignore'. Build the title part using the + first `bibtex-autokey-titlewords' words from this truncated title. + If the truncated title ends after up to `bibtex-autokey-titlewords' + + `bibtex-autokey-titlewords-stretch' words, use all words from the + truncated title. + 3. For every title word that appears in `bibtex-autokey-titleword-abbrevs' + use the corresponding abbreviation (see documentation of this variable + for further detail). + 4. From every title word not generated by an abbreviation, take at least + `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-function'. + 6. Build the title part by concatenating all abbreviated title words with + the string `bibtex-autokey-titleword-separator' between any two. + +Concatenate the key: + 1. Concatenate `bibtex-autokey-prefix-string', the name part, the year + part and the title part. If the name part and the year part are both + non-empty insert `bibtex-autokey-name-year-separator' between the two. + If the title part and the year (or name) part are non-empty, insert + `bibtex-autokey-year-title-separator' between the two. + 2. If `bibtex-autokey-before-presentation-function' is non-nil, it must be + a function taking one argument. Call this function with the generated + key as the argument. Use the return value of this function (a string) + as the key. + 3. If `bibtex-autokey-edit-before-use' is non-nil, present the key in the + minibuffer to the user for editing. Insert the key given by the user." + (let* ((names (bibtex-autokey-get-names)) + (year (bibtex-autokey-get-year)) + (title (bibtex-autokey-get-title)) + (autokey (concat bibtex-autokey-prefix-string + names + (unless (or (equal names "") + (equal year "")) + bibtex-autokey-name-year-separator) + year + (unless (or (and (equal names "") + (equal year "")) + (equal title "")) + bibtex-autokey-year-title-separator) + title))) + (if bibtex-autokey-before-presentation-function + (funcall bibtex-autokey-before-presentation-function autokey) + autokey))) - (let* ((pnt (point)) - (min - (progn - (bibtex-beginning-of-entry) - (point))) - (max - (progn - (bibtex-end-of-entry) - (point))) - (namefield - (progn - (goto-char min) - (if (or - (re-search-forward "^[ \t]*author[ \t]*=" max t) - (re-search-forward "^[ \t]*editor[ \t]*=" max t)) - (let* (bibtex-help-message - (start (progn - (bibtex-find-text t) - (point))) - (end (progn - (bibtex-find-text nil) - (point)))) - (bibtex-autokey-change - (buffer-substring-no-properties start end) - bibtex-autokey-name-change-strings)) - ""))) - (namelist - (mapcar - (function - (lambda (fullname) - (bibtex-autokey-abbrev - (if (string-match "," fullname) - (substring fullname 0 (match-beginning 0)) - (progn - (if (string-match " [^ ]*$" fullname) - (substring - fullname (1+ (match-beginning 0))) - fullname))) - bibtex-autokey-name-length))) - ;; Gather all names into a list - (let (names - (counter 0)) - (while (and - (not (equal namefield "")) - (or - (not (numberp bibtex-autokey-names)) - (< counter bibtex-autokey-names))) - (if (string-match " and " namefield) - (progn - (setq - names - (append names - (list - (downcase - (substring - namefield 0 (match-beginning 0))))) - namefield - (substring namefield (match-end 0)))) - (setq names - (append names (list (downcase namefield))) - namefield "")) - (setq counter (1+ counter))) - names))) - (namepart (mapconcat (function (lambda (name) name)) - namelist - bibtex-autokey-name-separator)) - (yearfield - (progn - (goto-char min) - (if (re-search-forward - "^[ \t]*year[ \t]*=[ \t]*\\([0-9]*\\)" max t) - (buffer-substring-no-properties - (match-beginning 1) (match-end 1)) - ""))) - (yearpart - (if (equal yearfield "") - "" - (substring yearfield - (- (length yearfield) - bibtex-autokey-year-length)))) - (titlestring - (let ((case-fold-search t) - (titlefield - (progn - (goto-char min) - (if (re-search-forward - "^[ \t]*title[ \t]*=" max t) - (let* (bibtex-help-message - (start (progn - (bibtex-find-text t) - (point))) - (end (progn - (bibtex-find-text nil) - (point)))) - (bibtex-autokey-change - (buffer-substring-no-properties start end) - bibtex-autokey-titleword-change-strings)) - ""))) - case-fold-search - (index 0) - (numberofitems - (length bibtex-autokey-title-terminators))) - (while (< index numberofitems) - (if (string-match - (elt bibtex-autokey-title-terminators index) - titlefield) - (setq titlefield - (substring titlefield 0 (match-beginning 0)))) - (setq index (1+ index))) - titlefield)) - (titlelist - (mapcar - (function - (lambda (titleword) - (let ((abbrev - (assoc-of-regexp - titleword bibtex-autokey-titleword-abbrevs))) - (if abbrev - (elt abbrev 1) - (bibtex-autokey-abbrev - titleword - bibtex-autokey-titleword-length))))) - ;; Gather all titlewords into a list - (let (titlewords - titlewords-extra - case-fold-search - (counter 0) - (first t)) - (while (and - (not (equal titlestring "")) - (or - (not (numberp bibtex-autokey-titlewords)) - (< counter (+ - bibtex-autokey-titlewords - bibtex-autokey-titlewords-stretch)))) - (if (string-match "\\b[A-Z][A-Za-z0-9]*" titlestring) - (let* ((end-match (match-end 0)) - (titleword - (downcase (substring titlestring - (match-beginning 0) - end-match)))) - (if (or - (not (numberp bibtex-autokey-titlewords)) - (< counter bibtex-autokey-titlewords)) - (if (and - first - (member-of-regexp - titleword - bibtex-autokey-titleword-first-ignore)) - (setq counter -1) - (setq titlewords - (append titlewords (list titleword)))) - (setq - titlewords-extra - (append titlewords-extra (list titleword)))) - (setq titlestring - (substring titlestring end-match))) - (setq titlestring "")) - (setq first nil - counter (1+ counter))) - (if (string-match "\\b[A-Z][^ ]*\\b" titlestring) - titlewords - (append titlewords titlewords-extra))))) - (titlepart (mapconcat (function (lambda (name) name)) - titlelist - bibtex-autokey-titleword-separator)) - (autokey - (concat - namepart - (if (not - (or - (equal namepart "") - (equal yearpart ""))) - bibtex-autokey-name-year-separator) - yearpart - (if (not - (or - (and - (equal namepart "") - (equal yearpart "")) - (equal titlepart ""))) - bibtex-autokey-year-title-separator) - titlepart))) - (goto-char pnt) - autokey)) - -(defun bibtex-parse-keys (add &optional abortable) - ;; Sets bibtex-keys to the keys used in the whole (possibly - ;; restricted) buffer (either as entry keys or as crossref entries). - ;; If ADD is non-nil adds the new keys to bibtex-keys instead of - ;; simply resetting it. If ABORTABLE is non-nil abort on user input. - (if bibtex-maintain-sorted-entries - (let ((labels (if add - bibtex-keys)) - label - (case-fold-search t)) - (save-excursion + +(defun bibtex-global-key-alist () + "Return global key alist based on `bibtex-files'." + (if bibtex-files + (apply 'append + (mapcar (lambda (buf) + (with-current-buffer buf bibtex-reference-keys)) + (bibtex-files-expand t))) + bibtex-reference-keys)) + +(defun bibtex-read-key (prompt &optional key global) + "Read BibTeX key from minibuffer using PROMPT and default KEY. +If optional arg GLOBAL is non-nil, completion is based on the keys in +`bibtex-reference-keys' of `bibtex-files'," + (let (completion-ignore-case) + (completing-read prompt (if global (bibtex-global-key-alist) + bibtex-reference-keys) + nil nil key 'bibtex-key-history))) + +(defun bibtex-read-string-key (&optional key) + "Read BibTeX string key from minibuffer using default KEY." + (let ((completion-ignore-case t)) + (completing-read "String key: " bibtex-strings + nil nil key 'bibtex-key-history))) + +(defun bibtex-parse-keys (&optional abortable verbose) + "Set `bibtex-reference-keys' to the keys used in the whole buffer. +Find both entry keys and crossref entries. If ABORTABLE is non-nil abort +on user input. If VERBOSE is non-nil give messages about progress. +Return alist of keys if parsing was completed, `aborted' otherwise. +If `bibtex-parse-keys-fast' is non-nil, use fast but simplified algorithm +for parsing BibTeX keys. If parsing fails, try to set this variable to nil." + (let (ref-keys crossref-keys) + (save-excursion + (save-match-data + (if verbose + (bibtex-progress-message + (concat (buffer-name) ": parsing reference keys"))) + (catch 'userkey + (goto-char (point-min)) + (if bibtex-parse-keys-fast + (let ((case-fold-search t) + (re (concat bibtex-entry-head "\\|" + ",[ \t\n]*crossref[ \t\n]*=[ \t\n]*" + "\\(\"[^\"]*\"\\|{[^}]*}\\)[ \t\n]*[,})]"))) + (while (re-search-forward re nil t) + (if (and abortable (input-pending-p)) + ;; user has aborted by typing a key --> return `aborted' + (throw 'userkey 'aborted)) + (cond ((match-end 3) + ;; This is a crossref. + (let ((key (buffer-substring-no-properties + (1+ (match-beginning 3)) (1- (match-end 3))))) + (unless (assoc key crossref-keys) + (push (list key) crossref-keys)))) + ;; only keys of known entries + ((assoc-string (bibtex-type-in-head) + bibtex-entry-field-alist t) + ;; This is an entry. + (let ((key (bibtex-key-in-head))) + (unless (assoc key ref-keys) + (push (cons key t) ref-keys))))))) + + (let (;; ignore @String entries because they are handled + ;; separately by bibtex-parse-strings + (bibtex-sort-ignore-string-entries t) + bounds) + (bibtex-map-entries + (lambda (key beg end) + (if (and abortable + (input-pending-p)) + ;; user has aborted by typing a key --> return `aborted' + (throw 'userkey 'aborted)) + (if verbose (bibtex-progress-message)) + (unless (assoc key ref-keys) + (push (cons key t) ref-keys)) + (if (and (setq bounds (bibtex-search-forward-field "crossref" end)) + (setq key (bibtex-text-in-field-bounds bounds t)) + (not (assoc key crossref-keys))) + (push (list key) crossref-keys)))))) + + (dolist (key crossref-keys) + (unless (assoc (car key) ref-keys) (push key ref-keys))) + (if verbose + (bibtex-progress-message 'done)) + ;; successful operation --> return `bibtex-reference-keys' + (setq bibtex-reference-keys ref-keys)))))) + +(defun bibtex-parse-strings (&optional add abortable) + "Set `bibtex-strings' to the string definitions in the whole buffer. +If ADD is non-nil add the new strings to `bibtex-strings' instead of +simply resetting it. If ADD is an alist of strings, also add ADD to +`bibtex-strings'. If ABORTABLE is non-nil abort on user input. +Return alist of strings if parsing was completed, `aborted' otherwise." + (save-excursion + (save-match-data + (goto-char (point-min)) + (let ((strings (if (and add + (listp bibtex-strings)) + bibtex-strings)) + bounds key) + (if (listp add) + (dolist (string add) + (unless (assoc-string (car string) strings t) + (push string strings)))) + (catch 'userkey + (while (setq bounds (bibtex-search-forward-string)) + (if (and abortable + (input-pending-p)) + ;; user has aborted by typing a key --> return `aborted' + (throw 'userkey 'aborted)) + (setq key (bibtex-reference-key-in-string bounds)) + (unless (assoc-string key strings t) + (push (cons key (bibtex-text-in-string bounds t)) + strings)) + (goto-char (bibtex-end-of-text-in-string bounds))) + ;; successful operation --> return `bibtex-strings' + (setq bibtex-strings strings)))))) + +(defun bibtex-strings () + "Return `bibtex-strings'. Initialize this variable if necessary." + (if (listp bibtex-strings) bibtex-strings + (bibtex-parse-strings (bibtex-string-files-init)))) + +(defun bibtex-string-files-init () + "Return initialization for `bibtex-strings'. +Use `bibtex-predefined-strings' and BibTeX files `bibtex-string-files'." + (save-match-data + (let ((dirlist (split-string (or bibtex-string-file-path default-directory) + ":+")) + (case-fold-search) + string-files fullfilename compl bounds found) + ;; collect absolute file names of valid string files + (dolist (filename bibtex-string-files) + (unless (string-match "\\.bib\\'" filename) + (setq filename (concat filename ".bib"))) + ;; test filenames + (if (file-name-absolute-p filename) + (if (file-readable-p filename) + (push filename string-files) + (error "BibTeX strings file %s not found" filename)) + (dolist (dir dirlist) + (when (file-readable-p + (setq fullfilename (expand-file-name filename dir))) + (push fullfilename string-files) + (setq found t))) + (unless found + (error "File %s not in paths defined via bibtex-string-file-path" + filename)))) + ;; parse string files + (dolist (filename string-files) + (with-temp-buffer + (insert-file-contents filename) (goto-char (point-min)) - (if (not add) - (message "Parsing reference keys...")) - - (if (not - (catch 'userkey - (while - (re-search-forward - (concat - "\\(" - bibtex-reference-head - "\\)\\|\\(" - "^[ \t\n]*crossref[ \t\n]*=[ \t\n]*[{\"]\\([A-Za-z][]A-Za-z0-9.:;?!`'()/*@_+-]*\\)[}\"],?$" - "\\)") - nil t) - (if (and - abortable - (input-pending-p)) - (throw 'userkey t)) - (if (match-beginning (1+ bibtex-key-in-head)) - (setq - label - (buffer-substring-no-properties - (match-beginning (1+ bibtex-key-in-head)) - (match-end (1+ bibtex-key-in-head)))) - (setq - label - (buffer-substring-no-properties - (match-beginning (+ 3 bibtex-key-in-head)) - (match-end (+ 3 bibtex-key-in-head))))) - (if (not (assoc label labels)) - (setq labels - (cons (list label) labels)))))) - (progn - (setq - bibtex-buffer-last-parsed-for-keys-tick - (buffer-modified-tick)) - (if (not add) - (message "Parsing reference keys... done")) - (setq bibtex-keys labels))))))) - -(defun bibtex-auto-fill-function () - (let ((fill-prefix (make-string (+ bibtex-text-alignment 1) ? ))) - (do-auto-fill))) + (while (setq bounds (bibtex-search-forward-string)) + (push (cons (bibtex-reference-key-in-string bounds) + (bibtex-text-in-string bounds t)) + compl) + (goto-char (bibtex-end-of-string bounds))))) + (append bibtex-predefined-strings (nreverse compl))))) + +(defun bibtex-parse-buffers-stealthily () + "Parse buffer in the background during idle time. +Called by `run-with-idle-timer'. Whenever Emacs has been idle +for `bibtex-parse-keys-timeout' seconds, parse all BibTeX buffers +which have been modified after last parsing. +Parsing initializes `bibtex-reference-keys' and `bibtex-strings'." + (save-excursion + (let ((buffers (buffer-list)) + (strings-init (bibtex-string-files-init))) + (while (and buffers (not (input-pending-p))) + (set-buffer (car buffers)) + (if (and (eq major-mode 'bibtex-mode) + (not (eq (buffer-modified-tick) + 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. + (if (and (listp (bibtex-parse-keys t)) + ;; update bibtex-strings + (listp (bibtex-parse-strings strings-init t))) + + ;; remember that parsing was successful + (setq bibtex-buffer-last-parsed-tick (buffer-modified-tick))))) + (setq buffers (cdr buffers)))))) + +(defun bibtex-files-expand (&optional current force) + "Return an expanded list of BibTeX buffers based on `bibtex-files'. +Initialize in these buffers `bibtex-reference-keys' if not yet set. +List of BibTeX buffers includes current buffer if CURRENT is non-nil. +If FORCE is non-nil, (re)initialize `bibtex-reference-keys' even if +already set." + (let ((file-path (split-string (or bibtex-file-path default-directory) ":+")) + file-list dir-list buffer-list) + (dolist (file bibtex-files) + (cond ((eq file 'bibtex-file-path) + (setq dir-list (append dir-list file-path))) + ((file-accessible-directory-p file) + (push file dir-list)) + ((progn (unless (string-match "\\.bib\\'" file) + (setq file (concat file ".bib"))) + (file-name-absolute-p file)) + (push file file-list)) + (t + (let (fullfilename found) + (dolist (dir file-path) + (when (file-readable-p + (setq fullfilename (expand-file-name file dir))) + (push fullfilename file-list) + (setq found t))) + (unless found + (error "File %s not in paths defined via bibtex-file-path" + file)))))) + (dolist (file file-list) + (unless (file-readable-p file) + (error "BibTeX file %s not found" file))) + ;; expand dir-list + (dolist (dir dir-list) + (setq file-list + (append file-list (directory-files dir t "\\.bib\\'" t)))) + (delete-dups file-list) + (dolist (file file-list) + (when (file-readable-p file) + (push (find-file-noselect file) buffer-list) + (with-current-buffer (car buffer-list) + (if (or force (not (listp bibtex-reference-keys))) + (bibtex-parse-keys))))) + (cond ((and current (not (memq (current-buffer) buffer-list))) + (push (current-buffer) buffer-list) + (if force (bibtex-parse-keys))) + ((and (not current) (memq (current-buffer) buffer-list)) + (setq buffer-list (delq (current-buffer) buffer-list)))) + buffer-list)) + +(defun bibtex-complete-internal (completions) + "Complete word fragment before point to longest prefix of COMPLETIONS. +COMPLETIONS is an alist of strings. If point is not after the part +of a word, all strings are listed. Return completion." + ;; Return value is used by cleanup functions. + (let* ((case-fold-search t) + (beg (save-excursion + (re-search-backward "[ \t{\"]") + (forward-char) + (point))) + (end (point)) + (part-of-word (buffer-substring-no-properties beg end)) + (completion (try-completion part-of-word completions))) + (cond ((not completion) + (error "Can't find completion for `%s'" part-of-word)) + ((eq completion t) + part-of-word) + ((not (string= part-of-word completion)) + (delete-region beg end) + (insert completion) + 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") + 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))))) + (if abbr (message "Abbreviation for `%s'" abbr)) + (bibtex-remove-delimiters)))) + +(defun bibtex-complete-crossref-cleanup (key) + "Display summary message on entry KEY after completion of a crossref key. +Use `bibtex-summary-function' to generate summary." + (save-excursion + (if (and (stringp key) + (bibtex-find-entry key t)) + (message "Ref: %s" (funcall bibtex-summary-function))))) +(defun bibtex-copy-summary-as-kill () + "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")))) + +(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? + (if (looking-at bibtex-entry-maybe-empty-head) + (let* ((bibtex-autokey-name-case-convert-function 'identity) + (bibtex-autokey-name-length 'infty) + (bibtex-autokey-names 1) + (bibtex-autokey-names-stretch 0) + (bibtex-autokey-name-separator " ") + (bibtex-autokey-additional-names " etal") + (names (bibtex-autokey-get-names)) + (bibtex-autokey-year-length 4) + (year (bibtex-autokey-get-year)) + (bibtex-autokey-titlewords 5) + (bibtex-autokey-titlewords-stretch 2) + (bibtex-autokey-titleword-case-convert-function 'identity) + (bibtex-autokey-titleword-length 5) + (bibtex-autokey-titleword-separator " ") + (title (bibtex-autokey-get-title)) + (journal (bibtex-autokey-get-field + "journal" bibtex-autokey-transcriptions)) + (volume (bibtex-autokey-get-field "volume")) + (pages (bibtex-autokey-get-field "pages" '(("-.*\\'" . ""))))) + (mapconcat (lambda (arg) + (if (not (string= "" (cdr arg))) + (concat (car arg) (cdr arg)))) + `((" " . ,names) (" " . ,year) (": " . ,title) + (", " . ,journal) (" " . ,volume) (":" . ,pages)) + "")) + (error "Entry not found"))) + +(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'." + ;; 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 + (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 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 () + "Move point backward to beginning of field. +This function uses a simple, fast algorithm assuming that the field +begins at the beginning of a line. We use this function for font-locking." + (let ((field-reg (concat "^[ \t]*" bibtex-field-name "[ \t]*="))) + (beginning-of-line) + (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." + (let ((case-fold-search t) + (pnt (point)) + field 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 bounds (bibtex-parse-field-text)) + (progn + (setq start (car bounds) end (nth 1 bounds)) + ;; Always ignore field delimiters + (if (memq (char-before end) '(?\} ?\")) + (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))) + found)) + +(defun bibtex-font-lock-crossref (bound) + "Font-lock for crossref fields. BOUND limits the search." + (let ((case-fold-search t) + (pnt (point)) + (crossref-reg (concat "^[ \t]*crossref[ \t]*=[ \t\n]*" + "\\(\"[^\"]*\"\\|{[^}]*}\\)[ \t\n]*[,})]")) + start end found) + (bibtex-beginning-of-field) + (while (and (not found) + (re-search-forward crossref-reg bound t)) + (setq start (1+ (match-beginning 1)) + end (1- (match-end 1)) + found (>= start pnt))) + (if found (bibtex-button start end 'bibtex-find-crossref + (buffer-substring-no-properties start end) + start t)) + found)) + +(defun bibtex-button-action (button) + "Call BUTTON's BibTeX function." + (apply (button-get button 'bibtex-function) + (button-get button 'bibtex-args))) + +(define-button-type 'bibtex-url + 'action 'bibtex-button-action + 'bibtex-function 'bibtex-url + 'help-echo (purecopy "mouse-2, RET: follow URL")) + +(define-button-type 'bibtex-find-crossref + 'action 'bibtex-button-action + 'bibtex-function 'bibtex-find-crossref + 'help-echo (purecopy "mouse-2, RET: follow crossref")) + +(defun bibtex-button (beg end type &rest args) + "Make a BibTeX button from BEG to END of type TYPE in the current buffer." + (make-text-button beg end 'type type 'bibtex-args args)) ;; Interactive Functions: ;;;###autoload -(defun bibtex-mode () +(defun bibtex-mode () "Major mode for editing BibTeX files. -\\{bibtex-mode-map} - -A command such as \\[bibtex-Book] will outline the fields for a BibTeX book entry. - -The optional fields start with the string OPT, and thus ignored by BibTeX. -The OPT string may be removed from a field with \\[bibtex-remove-OPT]. -\\[bibtex-kill-optional-field] kills the current optional field entirely. -\\[bibtex-remove-double-quotes-or-braces] removes the double-quotes or -braces around the text of the current field. \\[bibtex-empty-field] -replaces the text of the current field with the default \"\" or {}. - -The command \\[bibtex-clean-entry] cleans the current entry, i.e. (i) removes -double-quotes or braces from entirely numerical fields, (ii) removes -OPT from all non-empty optional fields, (iii) removes all empty -optional fields, and (iv) checks that no non-optional fields are empty. - -Use \\[bibtex-find-text] to position the cursor at the end of the current field. -Use \\[bibtex-next-field] to move to end of the next field. - -The following may be of interest as well: - - Functions: - bibtex-entry - bibtex-print-help-message - bibtex-beginning-of-entry - bibtex-end-of-entry - bibtex-ispell-abstract - bibtex-narrow-to-entry - bibtex-hide-entry-bodies - bibtex-sort-entries - bibtex-validate-buffer - bibtex-pop-previous - bibtex-pop-next - bibtex-complete-string - - Variables: - bibtex-field-left-delimiter - bibtex-field-right-delimiter - bibtex-include-OPTcrossref - bibtex-include-OPTkey - bibtex-include-OPTannote - bibtex-mode-user-optional-fields - bibtex-clean-entry-zap-empty-opts - bibtex-sort-ignore-string-entries - bibtex-maintain-sorted-entries - bibtex-entry-field-alist - bibtex-predefined-strings - bibtex-string-files - ---------------------------------------------------------- -Entry to this mode calls the value of bibtex-mode-hook if that value is -non-nil." +General information on working with BibTeX mode: + +Use commands such as \\[bibtex-Book] to get a template for a specific entry. +Then fill in all desired fields using \\[bibtex-next-field] to jump from field +to field. After having filled in all desired fields in the entry, clean the +new entry with the command \\[bibtex-clean-entry]. + +Some features of BibTeX mode are available only by setting the variable +`bibtex-maintain-sorted-entries' to non-nil. However, then BibTeX mode +works only with buffers containing valid (syntactical correct) and sorted +entries. This is usually the case, if you have created a buffer completely +with BibTeX mode and finished every new entry with \\[bibtex-clean-entry]. + +For third party BibTeX files, call the command \\[bibtex-convert-alien] +to fully take advantage of all features of BibTeX mode. + + +Special information: + +A command such as \\[bibtex-Book] outlines the fields for a BibTeX book entry. + +The names of optional fields start with the string OPT, and are thus ignored +by BibTeX. The names of alternative fields from which only one is required +start with the string ALT. The OPT or ALT string may be removed from +the name of a field with \\[bibtex-remove-OPT-or-ALT]. +\\[bibtex-make-field] inserts a new field after the current one. +\\[bibtex-kill-field] kills the current field entirely. +\\[bibtex-yank] yanks the last recently killed field after the current field. +\\[bibtex-remove-delimiters] removes the double-quotes or braces around the text of the current field. +\\[bibtex-empty-field] replaces the text of the current field with the default \"\" or {}. +\\[bibtex-find-text] moves point to the end of the current field. +\\[bibtex-complete] completes word fragment before point according to context. + +The command \\[bibtex-clean-entry] cleans the current entry, i.e. it removes OPT/ALT +from the names of all non-empty optional or alternative fields, checks that +no required fields are empty, and does some formatting dependent on the value +of `bibtex-entry-format'. Furthermore, it can automatically generate a key +for the BibTeX entry, see `bibtex-generate-autokey'. +Note: some functions in BibTeX mode depend on entries being in a special +format (all fields beginning on separate lines), so it is usually a bad +idea to remove `realign' from `bibtex-entry-format'. + +BibTeX mode supports Imenu and hideshow minor mode (`hs-minor-mode'). + +---------------------------------------------------------- +Entry to BibTeX mode calls the value of `bibtex-mode-hook' +if that value is non-nil. + +\\{bibtex-mode-map}" (interactive) (kill-all-local-variables) (use-local-map bibtex-mode-map) (setq major-mode 'bibtex-mode) (setq mode-name "BibTeX") (set-syntax-table bibtex-mode-syntax-table) - (setq bibtex-completion-candidates bibtex-predefined-strings) - (mapcar - (function - (lambda (filename) - ;; collect pathnames - (let* ((bib (getenv "BIBINPUTS")) - (path (if bib - bib - ".")) - (dirs - (mapcar - (function - (lambda (dirname) ;; strips off trailing slashes - (let ((len (length dirname))) - (if (equal (elt dirname (1- len)) "/") - (substring dirname 0 (1- (1- len))) - dirname)))) - (let (actdirs) - (while (string-match ":" path) - (setq actdirs - (append actdirs - (list (substring - path 0 - (1- (match-end 0))))) - path (substring path (match-end 0)))) - (append actdirs (list path))))) - (filename (if (string-match "\.bib$" filename) - filename - (concat filename ".bib"))) - fullfilename - (item 0) - (size (length dirs))) - ;; test filenames - (while (and - (< item size) - (not (file-readable-p - (setq fullfilename - (concat (elt dirs item) "/" filename))))) - (setq item (1+ item))) - (if (< item size) - ;; file was found - (let ((curbuf (current-buffer)) - (bufname (make-temp-name "")) - (compl bibtex-completion-candidates)) - (create-file-buffer bufname) - (set-buffer bufname) - (insert-file-contents fullfilename) - (goto-char (point-min)) - (while (re-search-forward bibtex-string nil t) - (setq - compl - (append - compl - (list - (list (buffer-substring-no-properties - (match-beginning bibtex-key-in-string) - (match-end bibtex-key-in-string))))))) - (kill-buffer bufname) - (set-buffer curbuf) - (setq bibtex-completion-candidates compl)) - (error "File %s not in $BIBINPUTS paths" filename))))) - bibtex-string-files) - (add-hook - 'auto-save-hook - (function - (lambda () - (if (and - bibtex-maintain-sorted-entries - (eq major-mode 'bibtex-mode) - (not - (eq (buffer-modified-tick) - bibtex-buffer-last-parsed-for-keys-tick))) - (bibtex-parse-keys nil t))))) - (bibtex-parse-keys nil) - (make-local-variable 'paragraph-start) - (setq paragraph-start "[ \f\n\t]*$") - (make-local-variable 'comment-start) - (setq comment-start "%") - (auto-fill-mode 1) - (setq auto-fill-function 'bibtex-auto-fill-function) + (make-local-variable 'bibtex-buffer-last-parsed-tick) + ;; Install stealthy parse function if not already installed + (unless bibtex-parse-idle-timer + (setq bibtex-parse-idle-timer (run-with-idle-timer + bibtex-parse-keys-timeout t + 'bibtex-parse-buffers-stealthily))) + (set (make-local-variable 'paragraph-start) "[ \f\n\t]*$") + (set (make-local-variable 'comment-start) bibtex-comment-start) + (set (make-local-variable 'comment-start-skip) + (concat (regexp-quote bibtex-comment-start) "\\>[ \t]*")) + (set (make-local-variable 'comment-column) 0) + (set (make-local-variable 'defun-prompt-regexp) "^[ \t]*@[[:alnum:]]+[ \t]*") + (set (make-local-variable 'outline-regexp) "[ \t]*@") + (set (make-local-variable 'fill-paragraph-function) 'bibtex-fill-field) + (set (make-local-variable 'fill-prefix) (make-string (+ bibtex-entry-offset + bibtex-contline-indentation) + ?\s)) (set (make-local-variable 'font-lock-defaults) - '(bibtex-font-lock-keywords nil t ((?$ . "\"")))) - (run-hooks 'bibtex-mode-hook)) - -(defun bibtex-entry (entry-type &optional required optional) - "Inserts a new BibTeX entry. -Calls the value of bibtex-add-entry-hook if that value is non-nil." - (interactive (let* ((completion-ignore-case t) - (e-t (completing-read - "Entry Type: " - bibtex-entry-field-alist - nil t))) - (list e-t))) - (if (and (null required) (null optional)) - (let* ((e (assoc-ignore-case entry-type bibtex-entry-field-alist)) - (r-n-o (elt e 1)) - (c-ref (elt e 2))) - (if (null e) - (error "Bibtex entry type %s not defined!" entry-type)) - (if (and - (member entry-type bibtex-include-OPTcrossref) - c-ref) - (setq required (elt c-ref 0) - optional (elt c-ref 1)) - (setq required (elt r-n-o 0) - optional (elt r-n-o 1))))) - (let ((key - (if bibtex-maintain-sorted-entries - (completing-read - (format "%s key: " entry-type) - bibtex-keys)))) - (if bibtex-maintain-sorted-entries - (bibtex-find-entry-location key) - (bibtex-move-outside-of-entry)) - (insert "@" entry-type "{") - (if key - (insert key)) + '(bibtex-font-lock-keywords + nil t ((?$ . "\"") + ;; Mathematical expressions should be fontified as strings + (?\" . ".") + ;; Quotes are field delimiters and quote-delimited + ;; entries should be fontified in the same way as + ;; brace-delimited ones + ) + nil + (font-lock-syntactic-keywords . bibtex-font-lock-syntactic-keywords) + (font-lock-extra-managed-props . (category)) + (font-lock-mark-block-function + . (lambda () + (set-mark (bibtex-end-of-entry)) + (bibtex-beginning-of-entry))))) + (setq imenu-generic-expression + (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 + (easy-menu-add bibtex-edit-menu) + (easy-menu-add bibtex-entry-menu) + (run-mode-hooks 'bibtex-mode-hook)) + +(defun bibtex-field-list (entry-type) + "Return list of allowed fields for entry ENTRY-TYPE. +More specifically, the return value is a cons pair (REQUIRED . OPTIONAL), +where REQUIRED and OPTIONAL are lists of the required and optional field +names for ENTRY-TYPE according to `bibtex-entry-field-alist', +`bibtex-include-OPTkey', `bibtex-include-OPTcrossref', +and `bibtex-user-optional-fields'." + (let ((e (assoc-string entry-type bibtex-entry-field-alist t)) + required optional) + (unless e + (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)) + optional (nth 1 (nth 2 e))) + (setq required (nth 0 (nth 1 e)) + optional (nth 1 (nth 1 e)))) + (if bibtex-include-OPTkey + (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)) + bibtex-include-OPTkey)) + optional)) + (if (member-ignore-case entry-type bibtex-include-OPTcrossref) + (push '("crossref" "Reference key of the cross-referenced entry") + optional)) + (setq optional (append optional bibtex-user-optional-fields)) + (cons required optional))) + +(defun bibtex-entry (entry-type) + "Insert a new BibTeX entry of type ENTRY-TYPE. +After insertion call the value of `bibtex-add-entry-hook' if that value +is non-nil." + (interactive + (let ((completion-ignore-case t)) + (list (completing-read "Entry Type: " bibtex-entry-field-alist + nil t nil 'bibtex-entry-type-history)))) + (let ((key (if bibtex-maintain-sorted-entries + (bibtex-read-key (format "%s key: " entry-type)))) + (field-list (bibtex-field-list entry-type))) + (unless (bibtex-prepare-new-entry (list key nil entry-type)) + (error "Entry with key `%s' already exists" key)) + (indent-to-column bibtex-entry-offset) + (insert "@" entry-type (bibtex-entry-left-delimiter)) + (if key (insert key)) (save-excursion - (mapcar 'bibtex-make-field required) - (if (member entry-type bibtex-include-OPTcrossref) - (bibtex-make-optional-field '("crossref"))) - (if bibtex-include-OPTkey - (bibtex-make-optional-field '("key"))) - (mapcar 'bibtex-make-optional-field optional) - (mapcar 'bibtex-make-optional-field - bibtex-mode-user-optional-fields) - (if bibtex-include-OPTannote - (bibtex-make-optional-field '("annote"))) - (insert "\n}\n\n")) + (mapc 'bibtex-make-field (car field-list)) + (mapc 'bibtex-make-optional-field (cdr field-list)) + (if bibtex-comma-after-last-field + (insert ",")) + (insert "\n") + (indent-to-column bibtex-entry-offset) + (insert (bibtex-entry-right-delimiter) "\n\n")) (bibtex-next-field t) + (if (member-ignore-case entry-type bibtex-autofill-types) + (bibtex-autofill-entry)) (run-hooks 'bibtex-add-entry-hook))) -(defun bibtex-print-help-message () - "Prints helpful information about current field in current BibTeX entry." +(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. +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) + (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. +The alist elements have the form (FIELD . TEXT), where FIELD can also be +the special strings \"=type=\" and \"=key=\". For the FIELD \"=key=\" +TEXT may be nil. Remove \"OPT\" and \"ALT\" from FIELD. +Move point to the end of the last field. +If optional arg CONTENT is non-nil extract content of text fields." + (let (alist bounds) + (when (looking-at bibtex-entry-maybe-empty-head) + (push (cons "=type=" (bibtex-type-in-head)) alist) + (push (cons "=key=" (bibtex-key-in-head)) alist) + (goto-char (match-end 0)) + (while (setq bounds (bibtex-parse-field)) + (push (cons (bibtex-name-in-field bounds t) + (bibtex-text-in-field-bounds bounds content)) + alist) + (goto-char (bibtex-end-of-field bounds)))) + alist)) + +(defun bibtex-autofill-entry () + "Try to fill fields of current BibTeX entry based on neighboring entries. +The current entry must have a key. Determine the neighboring entry +\(previouse or next\) whose key is more similar to the key of the current +entry. For all empty fields of the current entry insert the corresponding +field contents of the neighboring entry. Finally try to update the text +based on the difference between the keys of the neighboring and the current +entry (for example, the year parts of the keys)." (interactive) - (let* ((pnt (point)) - (field-name - (progn - (beginning-of-line) - (condition-case errname - (bibtex-enclosing-regexp bibtex-field) - (search-failed - (goto-char pnt) - (error "Not on BibTeX field"))) - (let ((mb (match-beginning bibtex-name-in-field)) - (me (match-end bibtex-name-in-field))) - (goto-char mb) - (buffer-substring-no-properties - (if (looking-at "OPT") - (+ 3 mb) - mb) - me)))) - (reference-type - (progn - (re-search-backward - bibtex-reference-maybe-empty-head nil t) - (buffer-substring-no-properties - (1+ (match-beginning bibtex-type-in-head)) - (match-end bibtex-type-in-head)))) - (entry-list - (assoc-ignore-case reference-type - bibtex-entry-field-alist)) - (c-r-list (elt entry-list 2)) - (req-opt-list - (if (and - (member reference-type bibtex-include-OPTcrossref) - c-r-list) - c-r-list - (elt entry-list 1))) - (list-of-entries (append - (elt req-opt-list 0) - (elt req-opt-list 1) - bibtex-mode-user-optional-fields - (if (member - reference-type - bibtex-include-OPTcrossref) - '(("crossref" - "Label of the crossreferenced entry"))) - (if bibtex-include-OPTannote - '(("annote" - "Personal annotation (ignored)"))) - (if bibtex-include-OPTkey - '(("key" - "Key used for label creation if author and editor fields are missing")))))) - (goto-char pnt) - (if (assoc field-name list-of-entries) - (message (elt (assoc field-name list-of-entries) 1)) - (message "NO COMMENT AVAILABLE")))) - -(defun bibtex-make-field (e-t) - "Makes a field named E-T in current BibTeX entry." - (interactive "sBibTeX field name: ") - (let ((name (if (consp e-t) - (elt e-t 0) - e-t))) - (if (interactive-p) - (progn - (bibtex-find-text nil) - (if (looking-at "[}\"]") - (forward-char 1)))) - (insert ",\n") - (indent-to-column bibtex-name-alignment) - (insert name " = ") - (indent-to-column bibtex-text-alignment) - (insert bibtex-field-left-delimiter bibtex-field-right-delimiter) - (if (interactive-p) - (forward-char -1)))) - -(defun bibtex-make-optional-field (e-t) - "Makes an optional field named E-T in current BibTeX entry." - (if (consp e-t) - (setq e-t (cons (concat "OPT" (car e-t)) (cdr e-t))) - (setq e-t (concat "OPT" e-t))) - (bibtex-make-field e-t)) + (undo-boundary) ;So you can easily undo it, if it didn't work right. + (bibtex-beginning-of-entry) + (when (looking-at bibtex-entry-head) + (let ((type (bibtex-type-in-head)) + (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) + ;; First see whether to use the previous or the next entry + ;; for "inspiration". + (save-excursion + (goto-char (1- (match-beginning 0))) + (bibtex-beginning-of-entry) + (if (and (looking-at bibtex-entry-head) + (bibtex-string= type (bibtex-type-in-head)) + ;; In case we found ourselves :-( + (not (equal key (setq tmp (bibtex-key-in-head))))) + (setq other-key tmp + other (point)))) + (save-excursion + (bibtex-end-of-entry) + (bibtex-skip-to-valid-entry) + (if (and (looking-at bibtex-entry-head) + (bibtex-string= type (bibtex-type-in-head)) + ;; In case we found ourselves :-( + (not (equal key (setq tmp (bibtex-key-in-head)))) + (or (not other-key) + ;; Check which is the best match. + (< (length (try-completion "" (list key other-key))) + (length (try-completion "" (list key tmp)))))) + (setq other-key tmp + other (point)))) + ;; Then fill the new entry's fields with the chosen other entry. + (when other + (setq other (save-excursion (goto-char other) (bibtex-parse-entry))) + (setq key-end (point)) ;In case parse-entry changed the buffer. + (while (setq bounds (bibtex-parse-field)) + (let ((text (assoc-string (bibtex-name-in-field bounds t) + other t))) + (if (not (and text + (equal "" (bibtex-text-in-field-bounds bounds t)))) + (goto-char (bibtex-end-of-field bounds)) + (goto-char (bibtex-start-of-text-in-field bounds)) + (delete-region (point) (bibtex-end-of-text-in-field bounds)) + (insert (cdr text))))) + ;; Finally try to update the text based on the difference between + ;; the two keys. + (let* ((prefix (try-completion "" (list key other-key))) + ;; If the keys are foo91 and foo92, don't replace 1 for 2 + ;; but 91 for 92 instead. + (_ (if (string-match "[0-9]+\\'" prefix) + (setq prefix (substring prefix 0 (match-beginning 0))))) + (suffix (substring key (length prefix))) + (other-suffix (substring other-key (length prefix)))) + (while (re-search-backward (regexp-quote other-suffix) key-end 'move) + (replace-match suffix))))))) + +(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) + "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 +`bibtex-entry-field-alist'. +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." + (interactive + (list (let ((completion-ignore-case t) + (field-list (bibtex-field-list + (save-excursion + (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)) + nil nil nil bibtex-field-history)) + t t)) + (unless (consp field) + (setq field (list field))) + (when move + (bibtex-find-text) + (if (looking-at "[}\"]") + (forward-char))) + (insert ",\n") + (indent-to-column (+ bibtex-entry-offset bibtex-field-indentation)) + (if (nth 3 field) (insert "ALT")) + (insert (car field) " ") + (if bibtex-align-at-equal-sign + (indent-to-column (+ bibtex-entry-offset + (- bibtex-text-indentation 2)))) + (insert "= ") + (unless bibtex-align-at-equal-sign + (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)))))) + (when interactive + ;; (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. + "Move to beginning of BibTeX entry (beginning of line). If inside an entry, move to the beginning of it, otherwise move to the -beginning of the previous entry." +beginning of the previous entry. If point is ahead of all BibTeX entries +move point to the beginning of buffer. Return the new location of point." (interactive) - (if (looking-at "^@") + (skip-chars-forward " \t") + (if (looking-at "@") (forward-char)) - (re-search-backward "^@" nil 'move)) + (re-search-backward "^[ \t]*@" nil 'move) + (point)) (defun bibtex-end-of-entry () - "Move to end of BibTeX entry. + "Move to end of BibTeX entry (past the closing brace). If inside an entry, move to the end of it, otherwise move to the end -of the previous entry." +of the previous entry. Do not move if ahead of first entry. +Return the new location of point." (interactive) - (bibtex-beginning-of-entry) - (let ((parse-sexp-ignore-comments t)) - (forward-sexp 2) ;; skip entry type and body - )) - + (let ((case-fold-search t) + (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))) + ((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." + ;; code adapted from `goto-line' + (goto-char (point-min)) + (if (eq selective-display t) + (re-search-forward "[\n\C-m]" nil 'end (1- arg)) + (forward-line (1- arg)))) + +(defun bibtex-reposition-window () + "Make the current BibTeX entry visible. +If entry is smaller than `window-body-height', entry is centered in window. +Otherwise display the beginning of entry." + (interactive) + (let ((pnt (point)) + (beg (line-number-at-pos (bibtex-beginning-of-entry))) + (end (line-number-at-pos (bibtex-end-of-entry)))) + (if (> (window-body-height) (- end beg)) + ;; entry fits in current window + (progn + (bibtex-goto-line (/ (+ 1 beg end) 2)) + (recenter) + (goto-char pnt)) + ;; entry too large for current window + (bibtex-goto-line beg) + (recenter 0) + (if (> (1+ (- (line-number-at-pos pnt) beg)) + (window-body-height)) + (bibtex-goto-line beg) + (goto-char pnt))))) + +(defun bibtex-mark-entry () + "Put mark at beginning, point at end of current BibTeX entry." + (interactive) + (set-mark (bibtex-beginning-of-entry)) + (bibtex-end-of-entry)) + +(defun bibtex-count-entries (&optional count-string-entries) + "Count number of entries in current buffer or region. +With prefix argument COUNT-STRING-ENTRIES count all entries, +otherwise count all entries except @String entries. +If mark is active count entries in region, if not in whole buffer." + (interactive "P") + (let ((number 0) + (bibtex-sort-ignore-string-entries (not count-string-entries))) + (save-restriction + (if mark-active (narrow-to-region (region-beginning) (region-end))) + (bibtex-map-entries (lambda (key beg end) (setq number (1+ number))))) + (message "%s contains %d entries." + (if mark-active "Region" "Buffer") + number))) + (defun bibtex-ispell-entry () - "Spell whole BibTeX entry." + "Check BibTeX entry for spelling errors." (interactive) - (ispell-region (progn (bibtex-beginning-of-entry) (point)) - (progn (bibtex-end-of-entry) (point)))) + (ispell-region (save-excursion (bibtex-beginning-of-entry)) + (save-excursion (bibtex-end-of-entry)))) (defun bibtex-ispell-abstract () - "Spell abstract of BibTeX entry." + "Check abstract of BibTeX entry for spelling errors." (interactive) - (let ((pnt (bibtex-end-of-entry))) - (bibtex-beginning-of-entry) - (if (null - (re-search-forward "^[ \t]*[OPT]*abstract[ \t]*=" pnt)) - (error "No abstract in entry."))) - (ispell-region (point) - (save-excursion (forward-sexp) (point)))) + (let ((bounds (save-excursion + (bibtex-beginning-of-entry) + (bibtex-search-forward-field "abstract" t)))) + (if bounds + (ispell-region (bibtex-start-of-text-in-field bounds) + (bibtex-end-of-text-in-field bounds)) + (error "No abstract in entry")))) (defun bibtex-narrow-to-entry () "Narrow buffer to current BibTeX entry." (interactive) (save-excursion - (narrow-to-region (progn (bibtex-beginning-of-entry) (point)) - (progn (bibtex-end-of-entry) (point))))) - - -(defun bibtex-hide-entry-bodies (&optional arg) - "Hide all lines between first and last BibTeX entries not beginning with @. -With argument, show all text." + (widen) + (narrow-to-region (bibtex-beginning-of-entry) + (bibtex-end-of-entry)))) + +(defun bibtex-entry-index () + "Return index of BibTeX entry head at or past position of point. +The index is a list (KEY CROSSREF-KEY ENTRY-NAME) that is used for sorting +the entries of the BibTeX buffer. CROSSREF-KEY is nil unless the value +of `bibtex-maintain-sorted-entries' is `crossref'. Move point to the end +of the head of the entry found. Return nil if no entry found." + (let ((case-fold-search t)) + (if (re-search-forward bibtex-entry-maybe-empty-head nil t) + (let ((key (bibtex-key-in-head)) + ;; all entry names should be downcase (for ease of comparison) + (entry-name (downcase (bibtex-type-in-head)))) + ;; 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" 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 () + (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). +The predicate depends on the variable `bibtex-maintain-sorted-entries'. +If its value is nil use plain sorting." + (cond ((not index1) (not index2)) ; indices can be nil + ((not index2) nil) + ((eq bibtex-maintain-sorted-entries 'crossref) + (if (nth 1 index1) + (if (nth 1 index2) + (or (string-lessp (nth 1 index1) (nth 1 index2)) + (and (string-equal (nth 1 index1) (nth 1 index2)) + (string-lessp (nth 0 index1) (nth 0 index2)))) + (not (string-lessp (nth 0 index2) (nth 1 index1)))) + (if (nth 1 index2) + (string-lessp (nth 0 index1) (nth 1 index2)) + (string-lessp (nth 0 index1) (nth 0 index2))))) + ((eq bibtex-maintain-sorted-entries 'entry-class) + (let ((n1 (cdr (or (assoc (nth 2 index1) bibtex-sort-entry-class-alist) + (assoc 'catch-all bibtex-sort-entry-class-alist) + '(nil . 1000)))) ; if there is nothing else + (n2 (cdr (or (assoc (nth 2 index2) bibtex-sort-entry-class-alist) + (assoc 'catch-all bibtex-sort-entry-class-alist) + '(nil . 1000))))) ; if there is nothing else + (or (< n1 n2) + (and (= n1 n2) + (string-lessp (car index1) (car index2)))))) + (t ; (eq bibtex-maintain-sorted-entries 'plain) + (string-lessp (car index1) (car index2))))) + +(defun bibtex-sort-buffer () + "Sort BibTeX buffer alphabetically by key. +The predicate for sorting is defined via `bibtex-maintain-sorted-entries'. +If its value is nil use plain sorting. Text outside of BibTeX entries is not +affected. If `bibtex-sort-ignore-string-entries' is non-nil, @String entries +are ignored." + (interactive) + (bibtex-beginning-of-first-entry) ; Needed by `sort-subr' + (bibtex-init-sort-entry-class-alist) ; Needed by `bibtex-lessp'. + (sort-subr nil + 'bibtex-skip-to-valid-entry ; NEXTREC function + 'bibtex-end-of-entry ; ENDREC function + 'bibtex-entry-index ; STARTKEY function + nil ; ENDKEY function + 'bibtex-lessp)) ; PREDICATE + +(defun bibtex-find-crossref (crossref-key &optional pnt split) + "Move point to the beginning of BibTeX entry CROSSREF-KEY. +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." + (interactive + (let ((crossref-key + (save-excursion + (bibtex-beginning-of-entry) + (let ((bounds (bibtex-search-forward-field "crossref" t))) + (if bounds + (bibtex-text-in-field-bounds bounds t)))))) + (list (bibtex-read-key "Find crossref key: " crossref-key t) + (point) t))) + (let (buffer pos eqb) + (save-excursion + (setq pos (bibtex-find-entry crossref-key t) + buffer (current-buffer))) + (setq eqb (eq buffer (current-buffer))) + (cond ((not pos) + (if split (message "Crossref key `%s' not found" crossref-key))) + (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!"))) + ;; `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 + ;; the correct sorting order. + (eqb (goto-char pos)) + (t (set-buffer buffer) (goto-char pos))) + pos)) + +(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. +With prefix arg GLOBAL non-nil, search KEY in `bibtex-files'. +Otherwise the search is limited to the current buffer. +Optional arg START is buffer position where the search starts. +If it is nil, start search at beginning of buffer. +If DISPLAY is non-nil, display the buffer containing KEY. +Otherwise, use `set-buffer'. DISPLAY is t when called interactively." + (interactive (list (bibtex-read-key "Find key: " nil current-prefix-arg) + current-prefix-arg nil t)) + (if (and global bibtex-files) + (let ((buffer-list (bibtex-files-expand t)) + buffer found) + (while (and (not found) + (setq buffer (pop buffer-list))) + (with-current-buffer buffer + (if (cdr (assoc-string key bibtex-reference-keys)) + ;; `bibtex-find-entry' moves point if key found + (setq found (bibtex-find-entry key))))) + (cond ((and found display) + (let ((same-window-buffer-names + (cons (buffer-name buffer) same-window-buffer-names))) + (pop-to-buffer buffer) + (bibtex-reposition-window))) + (found (set-buffer buffer)) + (display (message "Key `%s' not found" key))) + found) + + (let* ((case-fold-search t) + (pnt (save-excursion + (goto-char (or start (point-min))) + (if (re-search-forward (concat "^[ \t]*\\(" + bibtex-entry-type + "\\)[ \t]*[({][ \t\n]*\\(" + (regexp-quote key) + "\\)[ \t\n]*[,=]") + nil t) + (match-beginning 0))))) + (cond (pnt + (goto-char pnt) + (if display (bibtex-reposition-window))) + (display (message "Key `%s' not found" key))) + pnt))) + +(defun bibtex-prepare-new-entry (index) + "Prepare a new BibTeX entry with index INDEX. +INDEX is a list (KEY CROSSREF-KEY ENTRY-NAME). +Move point where the entry KEY should be placed. +If `bibtex-maintain-sorted-entries' is non-nil, perform a binary +search to look for place for KEY. This requires that buffer is sorted, +see `bibtex-validate'. +Return t if preparation was successful or nil if entry KEY already exists." + (bibtex-init-sort-entry-class-alist) ; Needed by `bibtex-lessp'. + (let ((key (nth 0 index)) + key-exist) + (cond ((or (null key) + (and (stringp key) + (string-equal key "")) + (and (not (setq key-exist (bibtex-find-entry key))) + (not bibtex-maintain-sorted-entries))) + (bibtex-move-outside-of-entry)) + ;; 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 + (let* ((case-fold-search t) + (left (save-excursion (bibtex-beginning-of-first-entry))) + (bounds (save-excursion (goto-char (point-max)) + (bibtex-skip-to-valid-entry t))) + (right (if bounds (cdr bounds) (point-min))) + (found (if (>= left right) left)) + actual-index new) + (save-excursion + ;; Binary search + (while (not found) + (goto-char (/ (+ left right) 2)) + (bibtex-skip-to-valid-entry t) + (setq actual-index (bibtex-entry-index)) + (cond ((bibtex-lessp index actual-index) + (setq new (bibtex-beginning-of-entry)) + (if (equal right new) + (setq found right) + (setq right new))) + (t + (bibtex-end-of-entry) + (bibtex-skip-to-valid-entry) + (setq new (point)) + (if (equal left new) + (setq found right) + (setq left new)))))) + (goto-char found) + (bibtex-beginning-of-entry) + (setq actual-index (save-excursion (bibtex-entry-index))) + (when (or (not actual-index) + (bibtex-lessp actual-index index)) + ;; buffer contains no valid entries or + ;; greater than last entry --> append + (bibtex-end-of-entry) + (unless (bobp) (newline (forward-line 2))) + (beginning-of-line))))) + (unless key-exist t))) + +(defun bibtex-validate (&optional test-thoroughly) + "Validate if buffer or region is syntactically correct. +Check also for duplicate keys and correct sort order provided +`bibtex-maintain-sorted-entries' is non-nil. +With optional argument TEST-THOROUGHLY non-nil check also for +the absence of required fields and for questionable month fields. +If mark is active, validate current region, if not the whole buffer. +Only check known entry types, so you can put comments outside of entries. +Return t if test was successful, nil otherwise." + (interactive "P") + (let* ((case-fold-search t) + error-list syntax-error) + (save-excursion + (save-restriction + (if mark-active (narrow-to-region (region-beginning) (region-end))) + + ;; Check syntactical structure of entries + (goto-char (point-min)) + (bibtex-progress-message "Checking syntactical structure") + (let (bounds end) + (while (setq end (re-search-forward "^[ \t]*@" nil t)) + (bibtex-progress-message) + (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 + ;; Continue only if there were no syntax errors. + (setq syntax-error t) + + ;; 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) + (setq current (bibtex-entry-index)) + (cond ((not previous)) + ((member key key-list) + (push (cons (bibtex-current-line) + (format "Duplicate key `%s'" key)) + error-list)) + ((and bibtex-maintain-sorted-entries + (not (bibtex-lessp previous current))) + (push (cons (bibtex-current-line) + "Entries out of order") + error-list))) + (push key key-list) + (setq previous current))) + (bibtex-progress-message 'done)) + + ;; Check for duplicate keys in `bibtex-files'. + (bibtex-parse-keys) + ;; We don't want to be fooled by outdated `bibtex-reference-keys'. + (dolist (buffer (bibtex-files-expand nil t)) + (dolist (key (with-current-buffer buffer bibtex-reference-keys)) + (when (and (cdr key) + (cdr (assoc-string (car key) bibtex-reference-keys))) + (bibtex-find-entry (car key)) + (push (cons (bibtex-current-line) + (format "Duplicate key `%s' in %s" (car key) + (abbreviate-file-name (buffer-file-name buffer)))) + error-list)))) + + (when test-thoroughly + (bibtex-progress-message + "Checking required fields and month fields") + (let ((bibtex-sort-ignore-string-entries t)) + (bibtex-map-entries + (lambda (key beg end) + (bibtex-progress-message) + (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) + (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. + (let ((month (bibtex-text-in-field-bounds bounds))) + (not (or (string-match "\\`[\"{].+[\"}]\\'" month) + (assoc-string + month + bibtex-predefined-month-strings t))))) + (push (cons (bibtex-current-line) + "Questionable month field") + error-list)) + (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) + (setq alt-there t))) + (if (bibtex-string= field-name "crossref") + (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) + (push (car field) alt) + (push (cons (save-excursion (goto-char beg) + (bibtex-current-line)) + (format "Required field `%s' missing" + (car field))) + error-list))) + ;; The following fails if there are more than two + ;; alternatives in a BibTeX entry, which isn't + ;; the case momentarily. + (if (cdr alt) + (push (cons (save-excursion (goto-char beg) + (bibtex-current-line)) + (format "Alternative fields `%s'/`%s' missing" + (car alt) (cadr alt))) + error-list))))))) + (bibtex-progress-message 'done))))) + + (if error-list + (let ((file (file-name-nondirectory (buffer-file-name))) + (dir default-directory) + (err-buf "*BibTeX validation errors*")) + (setq error-list (sort error-list 'car-less-than-car)) + (with-current-buffer (get-buffer-create err-buf) + (setq default-directory dir) + (unless (eq major-mode 'compilation-mode) (compilation-mode)) + (toggle-read-only -1) + (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" + "\n")) + (dolist (err error-list) + (insert (format "%s:%d: %s\n" file (car err) (cdr err)))) + (set-buffer-modified-p nil) + (toggle-read-only 1) + (goto-line 3)) ; first error message + (display-buffer err-buf) + nil) ; return `nil' (i.e., buffer is invalid) + (message "%s is syntactically correct" + (if mark-active "Region" "Buffer")) + t))) ; return `t' (i.e., buffer is valid) + +(defun bibtex-validate-globally (&optional strings) + "Check for duplicate keys in `bibtex-files'. +With optional prefix arg STRINGS, check for duplicate strings, too. +Return t if test was successful, nil otherwise." (interactive "P") + (let ((buffer-list (bibtex-files-expand t)) + buffer-key-list current-buf current-keys error-list) + ;; Check for duplicate keys within BibTeX buffer + (dolist (buffer buffer-list) + (save-excursion + (set-buffer buffer) + (let (entry-type key key-list) + (goto-char (point-min)) + (while (re-search-forward bibtex-entry-head nil t) + (setq entry-type (bibtex-type-in-head) + key (bibtex-key-in-head)) + (if (or (and strings (bibtex-string= entry-type "string")) + (assoc-string entry-type bibtex-entry-field-alist t)) + (if (member key key-list) + (push (format "%s:%d: Duplicate key `%s'\n" + (buffer-file-name) + (bibtex-current-line) key) + error-list) + (push key key-list)))) + (push (cons buffer key-list) buffer-key-list)))) + + ;; Check for duplicate keys among BibTeX buffers + (while (setq current-buf (pop buffer-list)) + (setq current-keys (cdr (assq current-buf buffer-key-list))) + (with-current-buffer current-buf + (dolist (buffer buffer-list) + (dolist (key (cdr (assq buffer buffer-key-list))) + (when (assoc-string key current-keys) + (bibtex-find-entry key) + (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)))))) + + ;; Process error list + (if error-list + (let ((err-buf "*BibTeX validation errors*")) + (with-current-buffer (get-buffer-create err-buf) + (unless (eq major-mode 'compilation-mode) (compilation-mode)) + (toggle-read-only -1) + (delete-region (point-min) (point-max)) + (insert "BibTeX mode command `bibtex-validate-globally'\n\n") + (dolist (err (sort error-list 'string-lessp)) (insert err)) + (set-buffer-modified-p nil) + (toggle-read-only 1) + (goto-line 3)) ; first error message + (display-buffer err-buf) + nil) ; return `nil' (i.e., buffer is invalid) + (message "No duplicate keys.") + 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'. +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)) + (if (looking-at "[{\"]") + (forward-char))) + (goto-char (nth 2 bounds)) + (if (memq (preceding-char) '(?} ?\")) + (forward-char -1))) + (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 - (beginning-of-first-bibtex-entry) - ;; subst-char-in-region modifies the buffer, despite what the - ;; documentation says... - (let ((modifiedp (buffer-modified-p)) - (buffer-read-only nil)) - (if arg - (subst-char-in-region (point) (point-max) ?\r ?\n t) - (while (save-excursion (re-search-forward "\n[^@]" (point-max) t)) - ;; (save-excursion (replace-regexp "\n\\([^@]\\)" "\r\\1")) - (save-excursion - (while (re-search-forward "\n\\([^@]\\)" nil t) - (replace-match "\r\\1" nil nil))))) - (setq selective-display (not arg)) - (set-buffer-modified-p modifiedp)))) - -(defun bibtex-sort-entries () - "Sort BibTeX entries alphabetically by key. -Text outside of BibTeX entries is not affected. If -bibtex-sort-ignore-string-entries is non-nil, @string entries will be -ignored." - (interactive) - (save-restriction - (beginning-of-first-bibtex-entry) - (narrow-to-region - (point) - (save-excursion - (goto-char (point-max)) - (bibtex-end-of-entry) - (point))) - (if bibtex-sort-ignore-string-entries - (if (re-search-forward bibtex-reference nil 'move) - (goto-char (match-beginning 0)))) - (sort-subr - nil - ;; NEXTREC function - (function - (lambda () - (if bibtex-sort-ignore-string-entries - (if (re-search-forward bibtex-reference nil 'move) - (goto-char (match-beginning 0))) - (if (re-search-forward bibtex-reference-head nil 'move) - (goto-char (match-beginning 0)))))) - ;; ENDREC function - 'bibtex-end-of-entry - ;; STARTKEY function - (function - (lambda () - (if bibtex-sort-ignore-string-entries - (progn - (re-search-forward bibtex-reference) - (buffer-substring-no-properties - (match-beginning bibtex-key-in-reference) - (match-end bibtex-key-in-reference))) - (re-search-forward bibtex-reference-head) - (buffer-substring-no-properties - (match-beginning bibtex-key-in-head) - (match-end bibtex-key-in-head))))) - ;; ENDKEY function - nil))) - -(defun bibtex-find-entry-location (entry-name &optional ignore-dups) - "Looking for place to put the BibTeX entry named ENTRY-NAME. -Performs a binary search (therefore, buffer is assumed to be in sorted -order, without duplicates (see \\[bibtex-validate-buffer]), if it is -not, bibtex-find-entry-location will fail). If entry-name is already -used as a reference key, an error is signaled. However, if optional -variable IGNORE-DUPS is non-nil, no error messages about duplicate -entries are signaled, but the error handling is assumed to be made in -the calling function. Nil is returned, if an duplicate entry error -occurred, and t in all other cases." - (let* ((left - (progn - (beginning-of-first-bibtex-entry) - (if bibtex-sort-ignore-string-entries - (re-search-forward bibtex-reference nil `move) - (bibtex-end-of-entry)) - (point))) - (right - (progn - (goto-char (point-max)) - (if bibtex-sort-ignore-string-entries - (re-search-backward bibtex-reference nil `move) - (bibtex-beginning-of-entry)) - (point))) - actual-point - actual-key - (done (>= left right)) - new - dup) - (while (not done) - (setq actual-point (/ (+ left right) 2)) - (goto-char actual-point) + (let ((pnt (point)) + (bounds (bibtex-enclosing-field comma t)) + (case-fold-search t) + name start-text end-text end failure done no-sub string-const) (bibtex-beginning-of-entry) - (setq actual-key - (if bibtex-sort-ignore-string-entries - (progn - (re-search-forward bibtex-reference) - (buffer-substring-no-properties - (match-beginning bibtex-key-in-reference) - (match-end bibtex-key-in-reference))) - (re-search-forward bibtex-reference-head) - (buffer-substring-no-properties - (match-beginning bibtex-key-in-head) - (match-end bibtex-key-in-head)))) - (cond - ((string-lessp entry-name actual-key) - (setq new (match-beginning 0)) - (if (equal right new) - (setq done t) - (setq right new))) - ((string-lessp actual-key entry-name) - (setq new (match-end 0)) - (if (equal left new) - (setq done t) - (setq left new))) - ((string-equal actual-key entry-name) - (setq dup t - done t) - (if (not ignore-dups) - (error "Entry with key `%s' already exists!" entry-name))))) - (if dup - nil - (goto-char right) - (if (re-search-forward bibtex-reference nil t) - (progn - (setq actual-key - (buffer-substring-no-properties - (match-beginning bibtex-key-in-reference) - (match-end bibtex-key-in-reference))) - (if (string-lessp actual-key entry-name) - ;; even greater than last entry --> we must append + (cond (bounds + (setq name (bibtex-name-in-field bounds t) + 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 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 + ((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-text (match-end 0) + string-const t)) + (prog1 (setq bounds (bibtex-parse-field-string)) + (setq end-text (cdr bounds) + string-const nil))) (progn - (goto-char (match-end 0)) - (newline (forward-line 2)) - (beginning-of-line)) - (goto-char right)))) - t))) - -(defun bibtex-validate-buffer () - "Validate if the current BibTeX buffer is syntactically correct. -Any garbage (e.g. comments) before the first \"@\" is not tested (so -you can put comments here)." - (interactive) - (let ((pnt (point)) - (max (point-max))) - ;; looking if entries fit syntactical structure - (goto-char (point-min)) - (while (< (re-search-forward "@\\|\\'") max) - (forward-char -1) - (let ((p (point))) - (if (or - (looking-at "@string") - (looking-at "@preamble")) - (forward-char) - (if (not (and - (re-search-forward bibtex-reference nil t) - (equal p (match-beginning 0)))) - (progn - (goto-char p) - (error "Bad entry begins here")))))) - ;; looking if entries are balanced (a single non-escaped quote - ;; inside braces is not detected by the former check, but - ;; bibtex-sort-entries stumbles about it - (goto-char (point-min)) - (map-bibtex-entries - (function - (lambda (current) - (bibtex-beginning-of-entry) - (forward-sexp 2)))) - ;; looking for correct sort order and duplicates - (if bibtex-maintain-sorted-entries - (let ((entry-name (make-string 10 255)) - (previous nil) - point) - (beginning-of-first-bibtex-entry) - (map-bibtex-entries - (function - (lambda (current) - (cond ((or (null previous) - (string< previous current)) - (setq previous current - point (point))) - ((string-equal previous current) - (error "Duplicate here with previous!")) - (t - (error "Entries out of order here!")))))))) - (goto-char pnt) - (message "BibTeX buffer is syntactically correct"))) - -(defun bibtex-next-field (arg) - "Finds end of text of next BibTeX field; with arg, to its beginning." - (interactive "P") - (bibtex-inside-field) - (let ((start (point))) - (condition-case () - (progn - (bibtex-enclosing-field) - (goto-char (match-end 0)) - (forward-char 2)) - (error - (goto-char start) - (end-of-line) - (forward-char 1)))) - (bibtex-find-text arg)) - -(defun bibtex-find-text (arg) - "Go to end of text of current field; with arg, go to beginning." - (interactive "P") - (bibtex-inside-field) - (bibtex-enclosing-field) - (if arg - (progn - (goto-char (match-beginning bibtex-text-in-field)) - (if (looking-at "[{\"]") - (forward-char 1))) - (goto-char (match-end bibtex-text-in-field)) - (if (or - (= (preceding-char) ?}) - (= (preceding-char) ?\")) - (forward-char -1))) - (if bibtex-help-message - (bibtex-print-help-message))) - -(defun bibtex-remove-OPT () - "Removes the 'OPT' starting optional arguments and goes to end of text." - (interactive) - (bibtex-inside-field) - (bibtex-enclosing-field) + (if (and (<= start-text pnt) (<= pnt end-text)) + (setq done t) + (goto-char end-text)) + (if (looking-at "[ \t\n]*#[ \t\n]*") + (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. 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 comma))) + (save-excursion + (goto-char (bibtex-start-of-name-in-field bounds)) + (when (looking-at "OPT\\|ALT") + (delete-region (match-beginning 0) (match-end 0)) + ;; make field non-OPT + (search-forward "=") + (forward-char -1) + (delete-horizontal-space) + (if bibtex-align-at-equal-sign + (indent-to-column (- bibtex-text-indentation 2)) + (insert " ")) + (search-forward "=") + (delete-horizontal-space) + (if bibtex-align-at-equal-sign + (insert " ") + (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. Optional arg COMMA is as in +`bibtex-enclosing-field'. It is t for interactive calls." + (interactive (list current-prefix-arg t)) (save-excursion - (goto-char (match-beginning bibtex-name-in-field)) - (if (looking-at "OPT") - ;; sct@dcs.edinburgh.ac.uk - (progn - (delete-char (length "OPT")) - (search-forward "=") - (delete-horizontal-space) - (indent-to-column bibtex-text-alignment)))) - (bibtex-inside-field)) - -(defun bibtex-remove-double-quotes-or-braces () - "Removes \"\" or {} around string." - (interactive) + (let* ((case-fold-search t) + (bounds (bibtex-enclosing-field comma)) + (end (bibtex-end-of-field bounds)) + (beg (bibtex-start-of-field bounds))) + (goto-char end) + (skip-chars-forward ",") + (push (list (bibtex-name-in-field bounds) nil + (bibtex-text-in-field-bounds bounds)) + bibtex-field-kill-ring) + (if (> (length bibtex-field-kill-ring) bibtex-field-kill-ring-max) + (setcdr (nthcdr (1- bibtex-field-kill-ring-max) + bibtex-field-kill-ring) + nil)) + (setq bibtex-field-kill-ring-yank-pointer bibtex-field-kill-ring) + (unless copy-only + (delete-region beg end)))) + (setq bibtex-last-kill-command 'field)) + +(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. +With prefix arg COPY-ONLY, copy the current entry to `bibtex-entry-kill-ring', +but do not actually kill it." + (interactive "P") (save-excursion - (bibtex-inside-field) - (bibtex-enclosing-field) - (let ((start (match-beginning bibtex-text-in-field)) - (stop (match-end bibtex-text-in-field))) - (goto-char stop) - (forward-char -1) - (if (looking-at "[}\"]") - (delete-char 1)) - (goto-char start) - (if (looking-at "[{\"]") - (delete-char 1))))) - -(defun bibtex-kill-optional-field () - "Kill the entire enclosing optional BibTeX field." - (interactive) - (bibtex-inside-field) - (bibtex-enclosing-field) - (goto-char (match-beginning bibtex-name-in-field)) - (let ((the-end (match-end 0)) - (the-beginning (match-beginning 0))) - (if (looking-at "OPT") - (progn - (goto-char the-end) - (skip-chars-forward " \t\n,") - (kill-region the-beginning the-end)) - (error "Mandatory fields can't be killed")))) - -(defun bibtex-empty-field () - "Delete the text part of the current field, replace with empty text." + (let* ((case-fold-search t) + (beg (bibtex-beginning-of-entry)) + (end (progn (bibtex-end-of-entry) + (if (re-search-forward + bibtex-any-entry-maybe-empty-head nil 'move) + (goto-char (match-beginning 0))) + (point)))) + (push (buffer-substring-no-properties beg end) + bibtex-entry-kill-ring) + (if (> (length bibtex-entry-kill-ring) bibtex-entry-kill-ring-max) + (setcdr (nthcdr (1- bibtex-entry-kill-ring-max) + bibtex-entry-kill-ring) + nil)) + (setq bibtex-entry-kill-ring-yank-pointer bibtex-entry-kill-ring) + (unless copy-only + (delete-region beg end)))) + (setq bibtex-last-kill-command 'entry)) + +(defun bibtex-copy-entry-as-kill () + "Copy the entire enclosing BibTeX entry to `bibtex-entry-kill-ring'." (interactive) - (bibtex-inside-field) - (bibtex-enclosing-field) - (goto-char (match-beginning bibtex-text-in-field)) - (kill-region (point) (match-end bibtex-text-in-field)) - (insert (concat bibtex-field-left-delimiter - bibtex-field-right-delimiter)) - (bibtex-find-text t)) + (bibtex-kill-entry t)) + +(defun bibtex-yank (&optional n) + "Reinsert the last BibTeX item. +More precisely, reinsert the field or entry killed or yanked most recently. +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) 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'. 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. + +With no argument, the previous kill is inserted. +With argument N, insert the Nth previous kill. +If N is negative, this is a more recent kill. + +The sequence of kills wraps around, so that after the oldest one +comes the newest one." + (interactive "*p") + (unless (eq last-command 'bibtex-yank) + (error "Previous command was not a BibTeX yank")) + (setq this-command 'bibtex-yank) + (let ((inhibit-read-only t)) + (delete-region (point) (mark t)) + (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) + (bibtex-field-right-delimiter)) + (bibtex-find-text t nil bibtex-help-message))) (defun bibtex-pop-previous (arg) - "Replace text of current field with the text of similar field in previous entry. -With arg, go up ARG entries. Repeated, goes up so many times. May be + "Replace text of current field with the similar field in previous entry. +With arg, goes up ARG entries. Repeated, goes up so many times. May be intermixed with \\[bibtex-pop-next] (bibtex-pop-next)." (interactive "p") - (bibtex-inside-field) - (save-excursion - ; parse current field - (bibtex-enclosing-field) - (let ((start-old-text (match-beginning bibtex-text-in-field)) - (stop-old-text (match-end bibtex-text-in-field)) - (start-name (match-beginning bibtex-name-in-field)) - (stop-name (match-end bibtex-name-in-field)) - (new-text)) - (goto-char start-name) - ; construct regexp for previous field with same name as this one - (let ((matching-entry - (bibtex-cfield - (buffer-substring-no-properties (if (looking-at "OPT") - (+ (point) (length "OPT")) - (point)) - stop-name) - bibtex-field-text))) - ; if executed several times in a row, start each search where the - ; last one finished - (cond ((or (eq last-command 'bibtex-pop-previous) - (eq last-command 'bibtex-pop-next)) - t - ) - (t - (bibtex-enclosing-reference-maybe-empty-head) - (setq bibtex-pop-previous-search-point (point)) - (setq bibtex-pop-next-search-point (match-end 0)))) - (goto-char bibtex-pop-previous-search-point) - ; Now search for arg'th previous similar field - (cond - ((re-search-backward matching-entry (point-min) t arg) - (setq new-text - (buffer-substring-no-properties - (match-beginning bibtex-text-in-cfield) - (match-end bibtex-text-in-cfield))) - ;; change delimiters, if any changes needed - (cond - ((and - (equal bibtex-field-left-delimiter "{") - (eq (aref new-text 0) ?\") - (eq (aref new-text (1- (length new-text))) ?\")) - (aset new-text 0 ?\{) - (aset new-text (1- (length new-text)) ?\})) - ((and - (equal bibtex-field-left-delimiter "\"") - (eq (aref new-text 0) ?\{) - (eq (aref new-text (1- (length new-text))) ?\})) - (aset new-text 0 ?\") - (aset new-text (1- (length new-text)) ?\")) - ((or - (not (eq (aref new-text 0) - (aref bibtex-field-left-delimiter 0))) - (not (eq (aref new-text (1- (length new-text))) - (aref bibtex-field-right-delimiter 0)))) - (setq new-text (concat bibtex-field-left-delimiter - new-text - bibtex-field-right-delimiter)))) - ; Found a matching field. Remember boundaries. - (setq bibtex-pop-next-search-point (match-end 0)) - (setq bibtex-pop-previous-search-point (match-beginning 0)) - (bibtex-flash-head) - ; 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)) - (t ; search failed - (error "No previous matching BibTeX field.")))))) - (setq this-command 'bibtex-pop-previous)) + (bibtex-pop arg 'previous)) (defun bibtex-pop-next (arg) "Replace text of current field with the text of similar field in next entry. -With arg, go up ARG entries. Repeated, goes up so many times. May be +With arg, goes down ARG entries. Repeated, goes down so many times. May be intermixed with \\[bibtex-pop-previous] (bibtex-pop-previous)." (interactive "p") - (bibtex-inside-field) - (save-excursion - ; parse current field - (bibtex-enclosing-field) - (let ((start-old-text (match-beginning bibtex-text-in-field)) - (stop-old-text (match-end bibtex-text-in-field)) - (start-name (match-beginning bibtex-name-in-field)) - (stop-name (match-end bibtex-name-in-field)) - (new-text)) - (goto-char start-name) - ; construct regexp for next field with same name as this one, - ; ignoring possible OPT's - (let ((matching-entry - (bibtex-cfield - (buffer-substring-no-properties (if (looking-at "OPT") - (+ (point) (length "OPT")) - (point)) - stop-name) - bibtex-field-text))) - - ; if executed several times in a row, start each search where the - ; last one finished - (cond ((or (eq last-command 'bibtex-pop-next) - (eq last-command 'bibtex-pop-previous)) - t - ) - (t - (bibtex-enclosing-reference-maybe-empty-head) - (setq bibtex-pop-previous-search-point (point)) - (setq bibtex-pop-next-search-point (match-end 0)))) - (goto-char bibtex-pop-next-search-point) - - ; Now search for arg'th next similar field - (cond - ((re-search-forward matching-entry (point-max) t arg) - (setq new-text - (buffer-substring-no-properties - (match-beginning bibtex-text-in-cfield) - (match-end bibtex-text-in-cfield))) - ;; change delimiters, if any changes needed - (cond - ((and - (equal bibtex-field-left-delimiter "{") - (eq (aref new-text 0) ?\") - (eq (aref new-text (1- (length new-text))) ?\")) - (aset new-text 0 ?\{) - (aset new-text (1- (length new-text)) ?\})) - ((and - (equal bibtex-field-left-delimiter "\"") - (eq (aref new-text 0) ?\{) - (eq (aref new-text (1- (length new-text))) ?\})) - (aset new-text 0 ?\") - (aset new-text (1- (length new-text)) ?\")) - ((or - (not (eq (aref new-text 0) - (aref bibtex-field-left-delimiter 0))) - (not (eq (aref new-text (1- (length new-text))) - (aref bibtex-field-right-delimiter 0)))) - (setq new-text (concat bibtex-field-left-delimiter - new-text - bibtex-field-right-delimiter)))) - ; Found a matching field. Remember boundaries. - (setq bibtex-pop-next-search-point (match-end 0)) - (setq bibtex-pop-previous-search-point (match-beginning 0)) - (bibtex-flash-head) - ; 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)) - (t ; search failed - (error "No next matching BibTeX field.")))))) - (setq this-command 'bibtex-pop-next)) - -(defun bibtex-clean-entry (&optional arg) + (bibtex-pop arg 'next)) + +(defun bibtex-clean-entry (&optional new-key called-by-reformat) "Finish editing the current BibTeX entry and clean it up. -For all optional fields of current BibTeX entry: if empty, kill the -whole field; otherwise, remove the \"OPT\" string in the name; if text -numerical, remove double-quotes. For all mandatory fields: if empty, -signal error. If label of entry is empty or a prefix argument was -given, calculate a new entry label." +Check that no required fields are empty and formats entry dependent +on the value of `bibtex-entry-format'. +If the reference key of the entry is empty or a prefix argument is given, +calculate a new reference key. (Note: this works only if fields in entry +begin on separate lines prior to calling `bibtex-clean-entry' or if +'realign is contained in `bibtex-entry-format'.) +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 (interactive "P") - (bibtex-beginning-of-entry) - (let ((start (point)) - crossref-there) - (save-restriction - (narrow-to-region start (save-excursion (bibtex-end-of-entry) (point))) - (while (and - (re-search-forward bibtex-field (point-max) t 1) - (not crossref-there)) - ;; determine if reference has crossref entry - (let ((begin-name (match-beginning bibtex-name-in-field)) - (begin-text (match-beginning bibtex-text-in-field))) - (goto-char begin-name) - (if (looking-at "\\(OPTcrossref\\)\\|\\(crossref\\)") - (progn - (goto-char begin-text) - (if (not (looking-at - (concat - bibtex-field-left-delimiter - bibtex-field-right-delimiter))) - (setq crossref-there t)))))) - (bibtex-enclosing-reference-maybe-empty-head) - (re-search-forward bibtex-reference-type) - (let ((begin-type (1+ (match-beginning 0))) - (end-type (match-end 0))) - (goto-char start) - (while (re-search-forward bibtex-field (point-max) t 1) - (let ((begin-field (match-beginning 0)) - (end-field (match-end 0)) - (begin-name (match-beginning bibtex-name-in-field)) - (end-name (match-end bibtex-name-in-field)) - (begin-text (match-beginning bibtex-text-in-field)) - (end-text (match-end bibtex-text-in-field)) - ) - (goto-char begin-name) - (cond ((and - (looking-at "OPT") - bibtex-clean-entry-zap-empty-opts) - (goto-char begin-text) - (if (looking-at - (concat - bibtex-field-left-delimiter - bibtex-field-right-delimiter)) - ;; empty: delete whole field if really optional - ;; (missing crossref handled) or complain - (if (and - (not crossref-there) - (assoc - (downcase - (buffer-substring-no-properties - (+ (length "OPT") begin-name) end-name)) - (car (car (cdr - (assoc-ignore-case - (buffer-substring-no-properties - begin-type end-type) - bibtex-entry-field-alist)))))) - ;; field is not really optional - (progn - (goto-char begin-name) - (delete-char (length "OPT")) - ;; make field non-OPT - (search-forward "=") - (delete-horizontal-space) - (indent-to-column bibtex-text-alignment) - (forward-char) - ;; and loop to go through next test - (error "Mandatory field ``%s'' is empty" - (buffer-substring-no-properties - begin-name - end-name))) - ;; field is optional - (delete-region begin-field end-field)) - ;; otherwise: not empty, delete "OPT" - (goto-char begin-name) - (delete-char (length "OPT")) - (progn - ;; fixup alignment. [alarson:19920309.2047CST] - (search-forward "=") - (delete-horizontal-space) - (indent-to-column bibtex-text-alignment)) - (goto-char begin-field) ; and loop to go through next test - )) - (t - (goto-char begin-text) - (cond ((looking-at (concat - bibtex-field-left-delimiter - "[0-9]+" - bibtex-field-right-delimiter)) - ;; if numerical, - (goto-char end-text) - (delete-char -1) ; delete enclosing double-quotes - (goto-char begin-text) - (delete-char 1) - (goto-char end-field) ; go to end for next search - (forward-char -2) ; to compensate for the 2 quotes deleted - ) - ((looking-at (concat - bibtex-field-left-delimiter - bibtex-field-right-delimiter)) - ;; if empty quotes, complain - (forward-char 1) - (if (not (or (equal (buffer-substring-no-properties - begin-name - (+ begin-name 3)) - "OPT") - (equal (buffer-substring-no-properties - begin-name - (+ begin-name 3)) - "opt"))) - (error "Mandatory field ``%s'' is empty" - (buffer-substring-no-properties - begin-name end-name)))) - (t - (goto-char end-field))))))))) - (goto-char start) - (bibtex-end-of-entry)) - (let* ((eob (progn - (bibtex-end-of-entry) - (point))) - (key (progn - (bibtex-beginning-of-entry) - (if (re-search-forward - bibtex-reference-head eob t) - (buffer-substring-no-properties - (match-beginning bibtex-key-in-head) - (match-end bibtex-key-in-head)))))) - (if (or - arg - (not key)) - (progn - (let ((autokey - (if bibtex-autokey-edit-before-use - (read-from-minibuffer "Key to use: " - (bibtex-generate-autokey)) - (bibtex-generate-autokey)))) - (bibtex-beginning-of-entry) - (re-search-forward bibtex-reference-maybe-empty-head) - (if (match-beginning bibtex-key-in-head) - (delete-region (match-beginning bibtex-key-in-head) - (match-end bibtex-key-in-head))) - (insert autokey) - (let ((start (progn - (bibtex-beginning-of-entry) - (point))) - (end (progn - (bibtex-end-of-entry) - (re-search-forward "^@" nil 'move) - (beginning-of-line) - (point))) - last-command) - (kill-region start end) - (let ((success - (or - (not bibtex-maintain-sorted-entries) - (bibtex-find-entry-location autokey t)))) - (yank) - (setq kill-ring (cdr kill-ring)) - (forward-char -1) - (bibtex-beginning-of-entry) - (re-search-forward bibtex-reference-head) - (if (not success) - (error - "New inserted reference may be a duplicate.")))))))) - (save-excursion - (let ((start (progn (bibtex-beginning-of-entry) (point))) - (end (progn (bibtex-end-of-entry) (point)))) - (save-restriction - (narrow-to-region start end) - (bibtex-parse-keys t))))) - -(defun bibtex-complete-string () - "Complete word fragment before point to longest prefix of a defined string. -If point is not after the part of a word, all strings are listed." + (let ((case-fold-search t) + (start (bibtex-beginning-of-entry)) + (_ (or (looking-at bibtex-any-entry-maybe-empty-head) + (error "Not inside a BibTeX entry"))) + (entry-type (bibtex-type-in-head)) + (key (bibtex-key-in-head))) + ;; formatting + (cond ((bibtex-string= entry-type "preamble") + ;; (bibtex-format-preamble) + (error "No clean up of @Preamble entries")) + ((bibtex-string= entry-type "string") + (setq entry-type 'string)) + ;; (bibtex-format-string) + (t (bibtex-format-entry))) + ;; set key + (when (or new-key (not key)) + (setq key (bibtex-generate-autokey)) + ;; 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) + (bibtex-read-key "Key to use: " key)))) + (save-excursion + (re-search-forward (if (eq entry-type 'string) + bibtex-string-maybe-empty-head + bibtex-entry-maybe-empty-head)) + (if (match-beginning bibtex-key-in-head) + (delete-region (match-beginning bibtex-key-in-head) + (match-end bibtex-key-in-head))) + (insert key))) + + (unless called-by-reformat + (let* ((end (save-excursion + (bibtex-end-of-entry) + (if (re-search-forward + bibtex-entry-maybe-empty-head nil 'move) + (goto-char (match-beginning 0))) + (point))) + (entry (buffer-substring start end)) + ;; include the crossref key in index + (index (let ((bibtex-maintain-sorted-entries 'crossref)) + (bibtex-entry-index))) ; moves point to end of head + error) + ;; sorting + (if (and bibtex-maintain-sorted-entries + (not (and bibtex-sort-ignore-string-entries + (eq entry-type 'string)))) + (progn + (delete-region start end) + (setq error (not (bibtex-prepare-new-entry index)) + start (point)) ; update start + (save-excursion (insert entry))) + (bibtex-find-entry key) + (setq error (or (/= (point) start) + (bibtex-find-entry key nil end)))) + (if error + (error "New inserted entry yields duplicate key")) + (dolist (buffer (bibtex-files-expand)) + (with-current-buffer buffer + (if (cdr (assoc-string key bibtex-reference-keys)) + (error "Duplicate key in %s" (buffer-file-name))))) + + ;; Only update the list of keys if it has been built already. + (cond ((eq entry-type 'string) + (if (and (listp bibtex-strings) + (not (assoc key bibtex-strings))) + (push (cons key (bibtex-text-in-string + (bibtex-parse-string) t)) + bibtex-strings))) + ;; We have a normal entry. + ((listp bibtex-reference-keys) + (cond ((not (assoc key bibtex-reference-keys)) + (push (cons key t) bibtex-reference-keys)) + ((not (cdr (assoc key bibtex-reference-keys))) + ;; Turn a crossref key into a header key + (setq bibtex-reference-keys + (cons (cons key t) + (delete (list key) bibtex-reference-keys))))) + ;; Handle crossref key. + (if (and (nth 1 index) + (not (assoc (nth 1 index) bibtex-reference-keys))) + (push (list (nth 1 index)) bibtex-reference-keys))))) + + ;; final clean up + (if bibtex-clean-entry-hook + (save-excursion + (save-restriction + (bibtex-narrow-to-entry) + (run-hooks 'bibtex-clean-entry-hook))))))) + +(defun bibtex-fill-field-bounds (bounds justify &optional move) + "Fill BibTeX field delimited by BOUNDS. +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)))) + (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. +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 t))) + (bibtex-fill-field-bounds bounds justify) + (goto-char pnt))) + +(defun bibtex-fill-entry () + "Fill current BibTeX entry. +Realign entry, so that every field starts on a separate line. Field +names appear in column `bibtex-field-indentation', field text starts in +column `bibtex-text-indentation' and continuation lines start here, too. +If `bibtex-align-at-equal-sign' is non-nil, align equal signs, too." (interactive "*") - (let* ((end (point)) - (beg (save-excursion - (re-search-backward "[ \t{\"]") - (forward-char 1) - (point))) - (part-of-word (buffer-substring-no-properties beg end)) - (string-list (copy-sequence bibtex-completion-candidates)) - (case-fold-search t) - (completion (save-excursion - (progn - (while (re-search-backward - bibtex-string (point-min) t) - (setq string-list - (cons - (list - (buffer-substring-no-properties - (match-beginning bibtex-key-in-string) - (match-end bibtex-key-in-string))) - string-list))) - (setq string-list - (sort string-list - (lambda(x y) - (string-lessp - (car x) - (car y))))) - (try-completion part-of-word string-list))))) - (cond ((eq completion t) - (bibtex-remove-double-quotes-or-braces)) - ((null completion) - (error "Can't find completion for \"%s\"" part-of-word)) - ((not (string= part-of-word completion)) - (delete-region beg end) - (insert completion) - (if (assoc completion string-list) - (bibtex-remove-double-quotes-or-braces))) - (t - (message "Making completion list...") - (let ((list (all-completions part-of-word string-list))) - (with-output-to-temp-buffer "*Completions*" - (display-completion-list list))) - (message "Making completion list...done"))))) + (let ((pnt (copy-marker (point))) + (end (copy-marker (bibtex-end-of-entry))) + (beg (bibtex-beginning-of-entry)) ; move point + bounds) + (bibtex-delete-whitespace) + (indent-to-column bibtex-entry-offset) + (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) + (indent-to-column bibtex-entry-offset) + (goto-char pnt))) + +(defun bibtex-realign () + "Realign BibTeX entries such that they are separated by one blank line." + (goto-char (point-min)) + (let ((case-fold-search t) + (entry-type (concat "[ \t\n]*\\(" bibtex-entry-type "\\)"))) + ;; No blank lines prior to the first entry if there no + ;; non-white characters in front of it. + (when (looking-at entry-type) + (replace-match "\\1")) + ;; Entries are separated by one blank line. + (while (re-search-forward entry-type nil t) + (replace-match "\n\n\\1")) + ;; One blank line past the last entry if it is followed by + ;; non-white characters, no blank line otherwise. + (beginning-of-line) + (when (re-search-forward bibtex-entry-type nil t) + (bibtex-end-of-entry) + (bibtex-delete-whitespace) + (open-line (if (eobp) 1 2))))) + +(defun bibtex-reformat (&optional read-options) + "Reformat all BibTeX entries in buffer or region. +Without prefix argument, reformatting is based on `bibtex-entry-format'. +With prefix argument, read options for reformatting from minibuffer. +With \\[universal-argument] \\[universal-argument] prefix argument, reuse previous answers (if any) again. +If mark is active reformat entries in region, if not in whole buffer." + (interactive "*P") + (let* ((pnt (point)) + (use-previous-options + (and (equal (prefix-numeric-value read-options) 16) + (or bibtex-reformat-previous-options + bibtex-reformat-previous-reference-keys))) + (bibtex-entry-format + (cond (read-options + (if use-previous-options + bibtex-reformat-previous-options + (setq bibtex-reformat-previous-options + (mapcar (lambda (option) + (if (y-or-n-p (car option)) (cdr option))) + `(("Realign entries (recommended)? " . 'realign) + ("Remove empty optional and alternative fields? " . 'opts-or-alts) + ("Remove delimiters around pure numerical fields? " . 'numerical-fields) + (,(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) + ("Inherit booktitle? " . 'inherit-booktitle) + ("Force delimiters? " . 'delimiters) + ("Unify case of entry types and field names? " . 'unify-case)))))) + ;; 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)) + (t + (remove 'required-fields (push 'realign bibtex-entry-format))))) + (reformat-reference-keys + (if read-options + (if use-previous-options + bibtex-reformat-previous-reference-keys + (setq bibtex-reformat-previous-reference-keys + (y-or-n-p "Generate new reference keys automatically? "))))) + (bibtex-sort-ignore-string-entries t) + bibtex-autokey-edit-before-use) -(defun bibtex-Article () + (save-restriction + (if mark-active (narrow-to-region (region-beginning) (region-end))) + (if (memq 'realign bibtex-entry-format) + (bibtex-realign)) + (bibtex-progress-message "Formatting" 1) + (bibtex-map-entries (lambda (key beg end) + (bibtex-progress-message) + (bibtex-clean-entry reformat-reference-keys t))) + (bibtex-progress-message 'done)) + (when reformat-reference-keys + (kill-local-variable 'bibtex-reference-keys) + (when bibtex-maintain-sorted-entries + (bibtex-progress-message "Sorting" 1) + (bibtex-sort-buffer) + (bibtex-progress-message 'done))) + (goto-char pnt))) + +(defun bibtex-convert-alien (&optional read-options) + "Make an alien BibTeX buffer fully usable by BibTeX mode. +If a file does not conform with all standards used by BibTeX mode, +some of the high-level features of BibTeX mode are not available. +This function tries to convert current buffer to conform with these standards. +With prefix argument READ-OPTIONS non-nil, read options for reformatting +entries from minibuffer." + (interactive "*P") + (message "Starting to validate buffer...") + (sit-for 1 nil t) + (bibtex-realign) + (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."))) + +(defun bibtex-complete () + "Complete word fragment before point according to context. +If point is inside key or crossref field perform key completion based on +`bibtex-reference-keys'. Inside a month field perform key completion +based on `bibtex-predefined-month-strings'. Inside any other field +\(including a String or Preamble definition) perform string completion +based on `bibtex-strings'. +An error is signaled if point is outside key or BibTeX field." (interactive) + (let ((pnt (point)) + (case-fold-search t) + bounds name compl) + (save-excursion + (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) + compl (cond ((bibtex-string= name "crossref") + ;; point is in crossref field + 'crossref-key) + ((bibtex-string= name "month") + ;; point is in month field + bibtex-predefined-month-strings) + ;; point is in other field + (t (bibtex-strings)))) + (bibtex-beginning-of-entry) + (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)))) + (setq compl 'string)) + ;; point is inside a @String field + ((and (>= pnt (bibtex-start-of-text-in-string bounds)) + (<= pnt (bibtex-end-of-text-in-string bounds))) + (setq compl (bibtex-strings))))) + ;; point is inside a @Preamble field + ((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) + (>= pnt (match-beginning bibtex-key-in-head)) + (<= pnt (match-end bibtex-key-in-head))) + ;; or point is on empty key + (and (not (match-beginning bibtex-key-in-head)) + (= pnt (match-end 0))))) + (setq compl 'key))))) + + (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) + (setq choose-completion-string-functions nil) + (choose-completion-string choice buffer base-size) + (bibtex-complete-crossref-cleanup choice) + 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))) + + (compl + ;; string completion + (let ((completion-ignore-case t)) + (setq choose-completion-string-functions + `(lambda (choice buffer mini-p base-size) + (setq choose-completion-string-functions nil) + (choose-completion-string choice buffer base-size) + (bibtex-complete-string-cleanup choice ',compl) + t)) ; needed by choose-completion-string-functions + (bibtex-complete-string-cleanup (bibtex-complete-internal compl) + compl))) + + (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'." + (interactive "*") (bibtex-entry "Article")) (defun bibtex-Book () - (interactive) + "Insert a new BibTeX @Book entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "Book")) (defun bibtex-Booklet () - (interactive) + "Insert a new BibTeX @Booklet entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "Booklet")) (defun bibtex-InBook () - (interactive) + "Insert a new BibTeX @InBook entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "InBook")) (defun bibtex-InCollection () - (interactive) + "Insert a new BibTeX @InCollection entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "InCollection")) (defun bibtex-InProceedings () - (interactive) + "Insert a new BibTeX @InProceedings entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "InProceedings")) (defun bibtex-Manual () - (interactive) + "Insert a new BibTeX @Manual entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "Manual")) (defun bibtex-MastersThesis () - (interactive) + "Insert a new BibTeX @MastersThesis entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "MastersThesis")) (defun bibtex-Misc () - (interactive) + "Insert a new BibTeX @Misc entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "Misc")) (defun bibtex-PhdThesis () - (interactive) + "Insert a new BibTeX @PhdThesis entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "PhdThesis")) (defun bibtex-Proceedings () - (interactive) + "Insert a new BibTeX @Proceedings entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "Proceedings")) (defun bibtex-TechReport () - (interactive) + "Insert a new BibTeX @TechReport entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "TechReport")) (defun bibtex-Unpublished () - (interactive) + "Insert a new BibTeX @Unpublished entry; see also `bibtex-entry'." + (interactive "*") (bibtex-entry "Unpublished")) -(defun bibtex-string () - (interactive) +(defun bibtex-String (&optional key) + "Insert a new BibTeX @String entry with key KEY." + (interactive (list (bibtex-read-string-key))) + (let ((bibtex-maintain-sorted-entries + (unless bibtex-sort-ignore-string-entries + bibtex-maintain-sorted-entries)) + endpos) + (unless (bibtex-prepare-new-entry (list key nil "String")) + (error "Entry with key `%s' already exists" key)) + (if (zerop (length key)) (setq key nil)) + (indent-to-column bibtex-entry-offset) + (insert "@String" + (bibtex-entry-left-delimiter)) + (if key + (insert key) + (setq endpos (point))) + (insert " = " + (bibtex-field-left-delimiter)) + (if key + (setq endpos (point))) + (insert (bibtex-field-right-delimiter) + (bibtex-entry-right-delimiter) + "\n") + (goto-char endpos))) + +(defun bibtex-Preamble () + "Insert a new BibTeX @Preamble entry." + (interactive "*") (bibtex-move-outside-of-entry) - (insert - (concat - "@string{ = " - bibtex-field-left-delimiter - bibtex-field-right-delimiter - "}\n")) - (forward-line -1) - (forward-char 8)) - -(defun bibtex-preamble () + (indent-to-column bibtex-entry-offset) + (insert "@Preamble" + (bibtex-entry-left-delimiter) + (bibtex-field-left-delimiter)) + (let ((endpos (point))) + (insert (bibtex-field-right-delimiter) + (bibtex-entry-right-delimiter) + "\n") + (goto-char endpos))) + +(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' unless NO-BROWSE is nil. +Return the URL or nil if none can be generated." (interactive) - (bibtex-move-outside-of-entry) - (insert "@Preamble{}\n") - (forward-line -1) - (forward-char 10)) - + (save-excursion + (if pos (goto-char pos)) + (bibtex-beginning-of-entry) + ;; Always remove field delimiters + (let ((fields-alist (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) + (push (cond ((functionp (nth 2 step)) + (funcall (nth 2 step) field)) + ((numberp (nth 2 step)) + (match-string (nth 2 step) field)) + (t + (replace-match (nth 2 step) t nil field))) + obj) + ;; If the scheme is set up correctly, + ;; we should never reach this point + (error "Match failed: %s" field))) + (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))) ;; Make BibTeX a Feature (provide 'bibtex) - +;; arch-tag: ee2be3af-caad-427f-b42a-d20fad630d04 ;;; bibtex.el ends here