|
|
- ;;; bibtex-completion.el --- A BibTeX backend for completion frameworks
-
- ;; Author: Titus von der Malsburg <malsburg@posteo.de>
- ;; Justin Burkett <justin@burkett.cc>
- ;; Maintainer: Titus von der Malsburg <malsburg@posteo.de>
- ;; Version: 1.0.0
- ;; Package-Requires: ((parsebib "1.0") (s "1.9.0") (dash "2.6.0") (f "0.16.2") (cl-lib "0.5"))
-
- ;; This program is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation, either version 3 of the License, or
- ;; (at your option) any later version.
-
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Commentary:
-
- ;; A BibTeX backend for completion frameworks
-
- ;; There are currently two fronends: helm-bibtex and ivy-bibtex.
- ;;
- ;; See the github page for details:
- ;;
- ;; https://github.com/tmalsburg/helm-bibtex
-
- ;;; Code:
-
- (require 'browse-url)
- (require 'parsebib)
- (require 'cl-lib)
- (require 'dash)
- (require 's)
- (require 'f)
- (require 'biblio)
- (require 'filenotify)
-
- ;; Silence byte-compiler
- (declare-function reftex-what-macro "reftex-parse")
- (declare-function reftex-get-bibfile-list "reftex-cite")
- (declare-function outline-show-all "outline")
- (declare-function org-narrow-to-subtree "org")
- (declare-function org-cycle-hide-drawers "org")
- (declare-function org-find-property "org")
- (declare-function org-show-entry "org")
- (declare-function org-entry-get "org")
- (declare-function org-element-parse-buffer "org-element")
- (declare-function org-element-map "org-element")
- (declare-function org-element-property "org-element")
-
- (defgroup bibtex-completion nil
- "Helm plugin for searching entries in a BibTeX bibliography."
- :group 'completion)
-
- (defcustom bibtex-completion-bibliography nil
- "The BibTeX file or list of BibTeX files. Org-bibtex users can
- also specify org-mode bibliography files, in which case it will
- be assumed that a BibTeX file exists with the same name and
- extension bib instead of org. If the bib file has a different
- name, use a cons cell (\"orgfile.org\" . \"bibfile.bib\") instead."
- :group 'bibtex-completion
- :type '(choice file (repeat file)))
-
- (defcustom bibtex-completion-library-path nil
- "A directory or list of directories in which PDFs are stored.
- Bibtex-completion assumes that the names of these PDFs are
- composed of the BibTeX-key plus a \".pdf\" suffix."
- :group 'bibtex-completion
- :type '(choice directory (repeat directory)))
-
- (defcustom bibtex-completion-pdf-open-function 'find-file
- "The function used for opening PDF files. This can be an
- arbitrary function that takes one argument: the path to the PDF
- file. The default is `find-file' which opens the PDF in
- Emacs (either with docview or, if installed, the much superior
- pdf-tools. When set to `helm-open-file-with-default-tool', the
- systems default viewer for PDFs is used."
- :group 'bibtex-completion
- :type 'function)
-
- (defcustom bibtex-completion-pdf-extension ".pdf"
- "The extension of a BibTeX entry's \"PDF\" file. This makes it
- possible to use another file type. It can also be a list of
- file types, which are then tried sequentially until a file is
- found. Beware that adding file types can reduce performance for
- large bibliographies. This variable has no effect if PDFs are
- referenced via the file field."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-find-additional-pdfs nil
- "If non-nil, all files whose base name starts with the BibTeX
- key and ends with `bibtex-completion-pdf-extension' are
- considered as PDFs, not only \"<key>.<extension>\". Note that
- for performance reasons, an entry is only marked as having a
- PDF if \"<key>.<extension\" exists."
- :group 'bibtex-completion
- :type 'boolean)
-
- (defcustom bibtex-completion-pdf-symbol "⌘"
- "Symbol used to indicate that a PDF file is available for a
- publication. This should be a single character."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-format-citation-functions
- '((org-mode . bibtex-completion-format-citation-ebib)
- (latex-mode . bibtex-completion-format-citation-cite)
- (markdown-mode . bibtex-completion-format-citation-pandoc-citeproc)
- (default . bibtex-completion-format-citation-default))
- "The functions used for formatting citations. The publication
- can be cited, for example, as \cite{key} or ebib:key depending on
- the major mode of the current buffer. Note that the functions
- should accept a list of keys as input. With multiple marked
- entries one can insert multiple keys at once,
- e.g. \cite{key1,key2}. See the functions
- `bibtex-completion-format-citation-ebib' and
- `bibtex-completion-format-citation-cite' as examples."
- :group 'bibtex-completion
- :type '(alist :key-type symbol :value-type function))
-
- (defcustom bibtex-completion-notes-path nil
- "The place where notes are stored. This is either a file, in
- which case all notes are stored in that file, or a directory, in
- which case each publication gets its own notes file in that
- directory. In the latter case, bibtex-completion assumes that the
- names of the note files are composed of the BibTeX-key plus a
- suffix that is specified in `bibtex-completion-notes-extension'."
- :group 'bibtex-completion
- :type '(choice file directory (const nil)))
-
- (defcustom bibtex-completion-notes-template-multiple-files
- "#+TITLE: Notes on: ${author-or-editor} (${year}): ${title}\n\n"
- "Template used to create a new note when each note is stored in a
- separate file. '${field-name}' can be used to insert the value of a
- BibTeX field into the template. Apart from the fields defined in the
- entry, one can also use the virtual fields `author-or-editor` -- which
- contains the author names if defined and otherwise the names of the
- editors -- and `author-abbrev` -- which abbreviates to 'First author
- et al.' when there are three or more authors."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-notes-template-one-file
- "\n* ${author-or-editor} (${year}): ${title}\n :PROPERTIES:\n :Custom_ID: ${=key=}\n :END:\n\n"
- "Template used to create a new note when all notes are stored
- in one file. '${field-name}' can be used to insert the value of
- a BibTeX field into the template. Apart from the fields defined in
- the entry, one can also use the virtual field `author-or-editor` -- which
- contains the author names if defined and otherwise the names of the
- editors -- and `author-abbrev` -- which abbreviates to 'First author
- et al.' when there are three or more authors."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-notes-key-pattern
- ":Custom_ID: +%s\\( \\|$\\)"
- "The pattern used to find entries in the notes file. Only
- relevant when all notes are stored in one file. The key can be
- inserted into the pattern using the `format` function."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-notes-extension ".org"
- "The extension of the files containing notes. This is only
- used when `bibtex-completion-notes-path' is a directory (not a file)."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-notes-symbol "✎"
- "Symbol used to indicate that a publication has notes. This
- should be a single character."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-fallback-options
- '(("CrossRef (biblio.el)"
- . (lambda (search-expression) (biblio-lookup #'biblio-crossref-backend search-expression)))
- ("arXiv (biblio.el)"
- . (lambda (search-expression) (biblio-lookup #'biblio-arxiv-backend search-expression)))
- ("DBLP (computer science bibliography) (biblio.el)"
- . (lambda (search-expression) (biblio--lookup-1 #'biblio-dblp-backend search-expression)))
- ("HAL (French open archive) (biblio.el)"
- . (lambda (search-expression) (biblio--lookup-1 #'biblio-hal-backend search-expression)))
- ("IEEE (biblio.el)"
- . (lambda (search-expression) (biblio--lookup-1 #'biblio-ieee-backend search-expression)))
- ("Google Scholar (web)"
- . "https://scholar.google.co.uk/scholar?q=%s")
- ("Pubmed (web)"
- . "https://www.ncbi.nlm.nih.gov/pubmed/?term=%s")
- ("Bodleian Library (web)"
- . "http://solo.bodleian.ox.ac.uk/primo_library/libweb/action/search.do?vl(freeText0)=%s&fn=search&tab=all")
- ("Library of Congress (web)"
- . "https://www.loc.gov/search/?q=%s&all=true&st=list")
- ("Deutsche Nationalbibliothek (web)"
- . "https://portal.dnb.de/opac.htm?query=%s")
- ("British National Library (web)"
- . "http://explore.bl.uk/primo_library/libweb/action/search.do?&vl(freeText0)=%s&fn=search")
- ("Bibliothèque nationale de France (web)"
- . "http://catalogue.bnf.fr/servlet/RechercheEquation?host=catalogue?historique1=Recherche+par+mots+de+la+notice&niveau1=1&url1=/jsp/recherchemots_simple.jsp?host=catalogue&maxNiveau=1&categorieRecherche=RechercheMotsSimple&NomPageJSP=/jsp/recherchemots_simple.jsp?host=catalogue&RechercheMotsSimpleAsauvegarder=0&ecranRechercheMot=/jsp/recherchemots_simple.jsp&resultatsParPage=20&x=40&y=22&nbElementsHDJ=6&nbElementsRDJ=7&nbElementsRCL=12&FondsNumerise=M&CollectionHautdejardin=TVXZROM&HDJ_DAV=R&HDJ_D2=V&HDJ_D1=T&HDJ_D3=X&HDJ_D4=Z&HDJ_SRB=O&CollectionRezdejardin=UWY1SPQM&RDJ_DAV=S&RDJ_D2=W&RDJ_D1=U&RDJ_D3=Y&RDJ_D4=1&RDJ_SRB=P&RDJ_RLR=Q&RICHELIEU_AUTRE=ABCDEEGIKLJ&RCL_D1=A&RCL_D2=K&RCL_D3=D&RCL_D4=E&RCL_D5=E&RCL_D6=C&RCL_D7=B&RCL_D8=J&RCL_D9=G&RCL_D10=I&RCL_D11=L&ARSENAL=H&LivrePeriodique=IP&partitions=C&images_fixes=F&son=S&images_animees=N&Disquette_cederoms=E&multimedia=M&cartes_plans=D&manuscrits=BT&monnaies_medailles_objets=JO&salle_spectacle=V&Monographie_TN=M&Periodique_TN=S&Recueil_TN=R&CollectionEditorial_TN=C&Ensemble_TN=E&Spectacle_TN=A&NoticeB=%s")
- ("Gallica Bibliothèque Numérique (web)"
- . "http://gallica.bnf.fr/Search?q=%s"))
- "Alist of online sources that can be used to search for
- publications. The key of each entry is the name of the online
- source. The value is the URL used for retrieving results. This
- URL must contain a %s in the position where the search term
- should be inserted. Alternatively, the value can be a function
- that will be called when the entry is selected."
- :group 'bibtex-completion
- :type '(alist :key-type string
- :value-type (choice (string :tag "URL")
- (function :tag "Function"))))
-
- (defcustom bibtex-completion-browser-function nil
- "The browser that is used to access online resources. If
- nil (default), the value of `browse-url-browser-function' is
- used. If that value is nil, Helm uses the first available
- browser in `helm-browse-url-default-browser-alist'"
- :group 'bibtex-completion
- :type '(choice
- (const :tag "Default" :value nil)
- (function-item :tag "Emacs interface to w3m" :value w3m-browse-url)
- (function-item :tag "Emacs W3" :value browse-url-w3)
- (function-item :tag "W3 in another Emacs via `gnudoit'"
- :value browse-url-w3-gnudoit)
- (function-item :tag "Mozilla" :value browse-url-mozilla)
- (function-item :tag "Firefox" :value browse-url-firefox)
- (function-item :tag "Chromium" :value browse-url-chromium)
- (function-item :tag "Galeon" :value browse-url-galeon)
- (function-item :tag "Epiphany" :value browse-url-epiphany)
- (function-item :tag "Netscape" :value browse-url-netscape)
- (function-item :tag "eww" :value eww-browse-url)
- (function-item :tag "Mosaic" :value browse-url-mosaic)
- (function-item :tag "Mosaic using CCI" :value browse-url-cci)
- (function-item :tag "Text browser in an xterm window"
- :value browse-url-text-xterm)
- (function-item :tag "Text browser in an Emacs window"
- :value browse-url-text-emacs)
- (function-item :tag "KDE" :value browse-url-kde)
- (function-item :tag "Elinks" :value browse-url-elinks)
- (function-item :tag "Specified by `Browse Url Generic Program'"
- :value browse-url-generic)
- (function-item :tag "Default Windows browser"
- :value browse-url-default-windows-browser)
- (function-item :tag "Default Mac OS X browser"
- :value browse-url-default-macosx-browser)
- (function-item :tag "GNOME invoking Mozilla"
- :value browse-url-gnome-moz)
- (function-item :tag "Default browser"
- :value browse-url-default-browser)
- (function :tag "Your own function")
- (alist :tag "Regexp/function association list"
- :key-type regexp :value-type function)))
-
- (defcustom bibtex-completion-additional-search-fields nil
- "The fields that are used for searching in addition to author,
- editor, title, year, BibTeX key, and entry type."
- :group 'bibtex-completion
- :type '(repeat string))
-
- (defcustom bibtex-completion-no-export-fields nil
- "A list of fields that should be ignored when exporting BibTeX
- entries."
- :group 'bibtex-completion
- :type '(repeat string))
-
- (defcustom bibtex-completion-cite-commands '("cite" "Cite" "parencite"
- "Parencite" "footcite" "footcitetext" "textcite" "Textcite"
- "smartcite" "Smartcite" "cite*" "parencite*" "supercite" "autocite"
- "Autocite" "autocite*" "Autocite*" "citeauthor" "Citeauthor"
- "citeauthor*" "Citeauthor*" "citetitle" "citetitle*" "citeyear"
- "citeyear*" "citedate" "citedate*" "citeurl" "nocite" "fullcite"
- "footfullcite" "notecite" "Notecite" "pnotecite" "Pnotecite"
- "fnotecite")
- "The list of LaTeX cite commands. When creating LaTeX
- citations, these can be accessed as future entries in the
- minibuffer history, i.e. by pressing the arrow down key. The
- default entries are taken from biblatex. There is currently no
- special support for multicite commands and volcite et al. These
- commands can be used but bibtex-completion does not prompt for their
- extra arguments."
- :group 'bibtex-completion
- :type '(choice string (repeat string)))
-
- (defcustom bibtex-completion-cite-default-command "cite"
- "The LaTeX cite command that is used if the user doesn't enter
- anything when prompted for such a command."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-cite-prompt-for-optional-arguments t
- "If t, bibtex-completion prompts for pre- and postnotes for
- LaTeX cite commands. Choose nil for no prompts."
- :group 'bibtex-completion
- :type 'boolean)
-
- (defcustom bibtex-completion-cite-default-as-initial-input nil
- "This variable controls how the default command defined in
- `bibtex-completion-cite-default-command' is used. If t, it is inserted
- into the minibuffer before reading input from the user. If nil,
- it is used as the default if the user doesn't enter anything."
- :group 'bibtex-completion
- :type 'boolean)
-
- (defcustom bibtex-completion-pdf-field nil
- "The name of the BibTeX field in which the path to PDF files is
- stored or nil if no such field should be used. If an entry has
- no value for this field, or if the specified file does not exist,
- or if this variable is nil, bibtex-completion will look up the PDF in
- the directories listed in `bibtex-completion-library-path'."
- :group 'bibtex-completion
- :type 'string)
-
- (defcustom bibtex-completion-display-formats
- '((t . "${author:36} ${title:*} ${year:4} ${=has-pdf=:1}${=has-note=:1} ${=type=:7}"))
- "Alist of format strings for displaying entries in the results list.
- The key of each element of this list is either a BibTeX entry
- type (in which case the format string applies to entries of this
- type only) or t (in which case the format string applies to all
- other entry types). The value is the format string.
-
- In the format string, expressions like \"${author:36}\",
- \"${title:*}\", etc, are expanded to the value of the
- corresponding field. An expression like \"${author:N}\" is
- truncated to a width of N characters, whereas an expression like
- \"${title:*}\" is truncated to the remaining width in the results
- window. Three special fields are available: \"=type=\" holds the
- BibTeX entry type, \"=has-pdf=\" holds
- `bibtex-completion-pdf-symbol' if the entry has a PDF file, and
- \"=has-notes=\" holds `bibtex-completion-notes-symbol' if the
- entry has a notes file. The \"author\" field is expanded to
- either the author names or, if the entry has no author field, the
- editor names."
- :group 'bibtex-completion
- :type '(alist :key-type symbol :value-type string))
-
- (defvar bibtex-completion-cross-referenced-entry-types
- '("proceedings" "mvproceedings" "book" "mvbook" "collection" "mvcollection")
- "The list of potentially cross-referenced entry types (in
- lowercase). Only entries of these types are checked in
- order to resolve cross-references. The default list is usually
- sufficient; adding more types can slow down resolution for
- large biblioraphies.")
-
- (defvar bibtex-completion-display-formats-internal nil
- "Stores `bibtex-completion-display-formats' together with the
- \"used width\" of each format string. This is set internally.")
-
- (defvar bibtex-completion-cache nil
- "A cache storing the hash of the bibliography content and the
- corresponding list of entries, for each bibliography file,
- obtained when the bibliography was last parsed. When the
- current bibliography hash is identical to the cached hash, the
- cached list of candidates is reused, otherwise the bibliography
- file is reparsed.")
-
- (defvar bibtex-completion-string-cache nil
- "A cache storing bibtex strings, for each bibliography file, obtained when the bibliography was last parsed.")
-
- (defvar bibtex-completion-string-hash-table nil
- "A hash table used for string replacements.")
-
- (defun bibtex-completion-normalize-bibliography (&optional type)
- "Returns a list of bibliography file(s) in
- `bibtex-completion-bibliography'. If there are org-mode
- bibliography-files, their corresponding bibtex files are listed
- as well, unless TYPE is 'main. If TYPE is 'bibtex, org-mode
- bibliography-files are instead replaced with their associated
- bibtex files."
- (delete-dups
- (cl-loop
- for bib-file in (-flatten (list bibtex-completion-bibliography))
- for main-file = (if (consp bib-file)
- (car bib-file)
- bib-file)
- for bibtex-file = (if (consp bib-file)
- (cdr bib-file)
- (concat (file-name-sans-extension main-file) ".bib"))
- unless (equal type 'bibtex)
- collect main-file
- unless (equal type 'main)
- collect bibtex-file)))
-
- (defvar bibtex-completion-file-watch-descriptors nil
- "List of file watches monitoring bibliography files for changes.")
-
- (defun bibtex-completion-init ()
- "Checks that the files and directories specified by the user
- actually exist. Also sets `bibtex-completion-display-formats-internal'."
-
- ;; Remove current watch-descriptors for bibliography files:
- (mapc (lambda (watch-descriptor)
- (file-notify-rm-watch watch-descriptor))
- bibtex-completion-file-watch-descriptors)
- (setq bibtex-completion-file-watch-descriptors nil)
-
- ;; Check that all specified bibliography files exist and add file
- ;; watches for automatic reloading of the bibliography when a file
- ;; is changed:
- (mapc (lambda (file)
- (if (f-file? file)
- (let ((watch-descriptor
- (file-notify-add-watch file
- '(change)
- (lambda (event) (bibtex-completion-candidates)))))
- (setq bibtex-completion-file-watch-descriptors
- (cons watch-descriptor bibtex-completion-file-watch-descriptors)))
- (user-error "Bibliography file %s could not be found." file)))
- (bibtex-completion-normalize-bibliography))
-
- ;; Pre-calculate minimal widths needed by the format strings for
- ;; various entry types:
- (setq bibtex-completion-display-formats-internal
- (mapcar (lambda (format)
- (let* ((format-string (cdr format))
- (fields-width 0)
- (string-width
- (length
- (s-format format-string
- (lambda (field)
- (setq fields-width
- (+ fields-width
- (string-to-number
- (or (cadr (split-string field ":"))
- ""))))
- "")))))
- (-cons* (car format) format-string (+ fields-width string-width))))
- bibtex-completion-display-formats)))
-
- (defun bibtex-completion-clear-cache (&optional files)
- "Clears FILES from cache. If FILES is omitted, all files in `bibtex-completion-biblography' are cleared."
- (setq bibtex-completion-cache
- (cl-remove-if
- (lambda (x)
- (member (car x)
- (or files
- (bibtex-completion-normalize-bibliography 'bibtex))))
- bibtex-completion-cache)))
-
- (defun bibtex-completion-clear-string-cache (&optional files)
- "Clears FILES from cache. If FILES is omitted, all files in `bibtex-completion-bibliography' are cleared."
- (setq bibtex-completion-string-cache
- (cl-remove-if
- (lambda (x)
- (member (car x)
- (or files
- (-flatten (list bibtex-completion-bibliography)))))
- bibtex-completion-string-cache)))
-
- (defun bibtex-completion-parse-strings (&optional ht-strings)
- "Parse the BibTeX strings listed in the current buffer and
- return a list of entries in the order in which they appeared in
- the BibTeX file.
-
- If HT-STRINGS is provided it is assumed to be a hash table used
- for string replacement."
- (goto-char (point-min))
- (let ((strings (cl-loop
- with ht = (if ht-strings ht-strings (make-hash-table :test #'equal))
- for entry-type = (parsebib-find-next-item)
- while entry-type
- if (string= (downcase entry-type) "string")
- collect (let ((entry (parsebib-read-string (point) ht)))
- (puthash (car entry) (cdr entry) ht)
- entry)
- )))
- (-filter (lambda (x) x) strings)))
-
- (defun bibtex-completion-update-strings-ht (ht strings)
- (cl-loop
- for entry in strings
- do (puthash (car entry) (cdr entry) ht)))
-
- (defvar bibtex-completion-cached-notes-keys nil
- "A cache storing notes keys obtained when the bibliography was last parsed.")
-
- (defun bibtex-completion-candidates ()
- "Reads the BibTeX files and returns a list of conses, one for
- each entry. The first element of these conses is a string
- containing authors, editors, title, year, type, and key of the
- entry. This is string is used for matching. The second element
- is the entry (only the fields listed above) as an alist."
- (let ((files (nreverse (bibtex-completion-normalize-bibliography 'bibtex)))
- (ht-strings (make-hash-table :test #'equal))
- reparsed-files)
-
- ;; Open each bibliography file in a temporary buffer,
- ;; check hash of bibliography and mark for reparsing if necessary:
-
- (cl-loop
- for file in files
- do
- (with-temp-buffer
- (insert-file-contents file)
- (let ((bibliography-hash (secure-hash 'sha256 (current-buffer))))
- (unless (string= (cadr (assoc file bibtex-completion-cache))
- bibliography-hash)
- ;; Mark file as reparsed.
- ;; This will be useful to resolve cross-references:
- (push file reparsed-files)))))
-
- (when (and bibtex-completion-notes-path
- (f-file? bibtex-completion-notes-path))
- (require 'org-element)
- (with-temp-buffer
- (org-mode) ;; need this to avoid error in emacs 25.3.1
- (insert-file-contents bibtex-completion-notes-path)
- (setq bibtex-completion-cached-notes-keys
- (let ((tree (org-element-parse-buffer 'headline)))
- (org-element-map tree 'headline
- (lambda (key) (org-element-property :CUSTOM_ID key)))))))
-
- ;; reparse if necessary
-
- (when reparsed-files
- (cl-loop
- for file in files
- do
- (with-temp-buffer
- (insert-file-contents file)
- (let ((bibliography-hash (secure-hash 'sha256 (current-buffer))))
- (if (not (member file reparsed-files))
- (bibtex-completion-update-strings-ht ht-strings
- (cddr (assoc file bibtex-completion-string-cache)))
- (progn
- (message "Parsing bibliography file %s ..." file)
- (bibtex-completion-clear-string-cache (list file))
- (push (-cons* file
- bibliography-hash
- (bibtex-completion-parse-strings ht-strings))
- bibtex-completion-string-cache)
-
- (bibtex-completion-clear-cache (list file))
- (push (-cons* file
- bibliography-hash
- (bibtex-completion-parse-bibliography ht-strings))
- bibtex-completion-cache))))))
- (setf bibtex-completion-string-hash-table ht-strings))
-
- ;; If some files were reparsed, resolve cross-references:
- (when reparsed-files
- (message "Resolving cross-references ...")
- (bibtex-completion-resolve-crossrefs files reparsed-files))
-
- ;; Finally return the list of candidates:
- (message "Done (re)loading bibliography.")
- (nreverse
- (cl-loop
- for file in files
- append (cddr (assoc file bibtex-completion-cache))))))
-
- (defun bibtex-completion-resolve-crossrefs (files reparsed-files)
- "Expand all entries with fields from cross-referenced entries
- in FILES, assuming that only those files in REPARSED-FILES were
- reparsed whereas the other files in FILES were up-to-date."
- (cl-loop
- with entry-hash = (bibtex-completion-make-entry-hash files reparsed-files)
- for file in files
- for entries = (cddr (assoc file bibtex-completion-cache))
- if (member file reparsed-files)
- ;; The file was reparsed.
- ;; Resolve crossrefs then make candidates for all entries:
- do (setf
- (cddr (assoc file bibtex-completion-cache))
- (cl-loop
- for entry in entries
- ;; Entries are alists of \(FIELD . VALUE\) pairs.
- for crossref = (bibtex-completion-get-value "crossref" entry)
- collect (bibtex-completion-make-candidate
- (if crossref
- (bibtex-completion-remove-duplicated-fields
- ;; Insert an empty field so we can discard the crossref info if needed:
- (append entry
- (cl-acons "" ""
- (gethash (downcase crossref) entry-hash))))
- entry))))
- else
- ;; The file was not reparsed.
- ;; Resolve crossrefs then make candidates for the entries with a crossref field:
- do (setf
- (cddr (assoc file bibtex-completion-cache))
- (cl-loop
- for entry in entries
- ;; Entries are \(STRING . ALIST\) conses.
- for entry-alist = (cdr entry)
- for crossref = (bibtex-completion-get-value "crossref" entry-alist)
- collect (if crossref
- (bibtex-completion-make-candidate
- (bibtex-completion-remove-duplicated-fields
- ;; Discard crossref info and resolve crossref again:
- (append (--take-while (> (length (car it)) 0) entry-alist)
- (cl-acons "" ""
- (gethash (downcase crossref) entry-hash)))))
- entry)))))
-
- (defun bibtex-completion-make-entry-hash (files reparsed-files)
- "Return a hash table of all potentially cross-referenced bibliography entries in FILES,
- assuming that only those files in REPARSED-FILES were reparsed
- whereas the other files in FILES were up-to-date. Only entries
- whose type belongs to
- `bibtex-completion-cross-referenced-entry-types' are included in
- the hash table."
- (cl-loop
- with entries =
- (cl-loop
- for file in files
- for entries = (cddr (assoc file bibtex-completion-cache))
- if (member file reparsed-files)
- ;; Entries are alists of \(FIELD . VALUE\) pairs.
- append entries
- ;; Entries are \(STRING . ALIST\) conses.
- else
- append (mapcar 'cdr entries))
- with ht = (make-hash-table :test #'equal :size (length entries))
- for entry in entries
- for key = (bibtex-completion-get-value "=key=" entry)
- if (member (downcase (bibtex-completion-get-value "=type=" entry))
- bibtex-completion-cross-referenced-entry-types)
- do (puthash (downcase key) entry ht)
- finally return ht))
-
- (defun bibtex-completion-make-candidate (entry)
- "Return a candidate for ENTRY."
- (cons (bibtex-completion-clean-string
- (s-join " " (-map #'cdr entry)))
- entry))
-
- (defun bibtex-completion-parse-bibliography (&optional ht-strings)
- "Parse the BibTeX entries listed in the current buffer and
- return a list of entries in the order in which they appeared in
- the BibTeX file. Also do some preprocessing of the entries.
-
- If HT-STRINGS is provided it is assumed to be a hash table."
- (goto-char (point-min))
- (cl-loop
- with fields = (append '("title" "crossref")
- (-map (lambda (it) (if (symbolp it) (symbol-name it) it))
- bibtex-completion-additional-search-fields))
- for entry-type = (parsebib-find-next-item)
- while entry-type
- unless (member-ignore-case entry-type '("preamble" "string" "comment"))
- collect (let* ((entry (parsebib-read-entry entry-type (point) ht-strings))
- (fields (append
- (list (if (assoc-string "author" entry 'case-fold)
- "author"
- "editor")
- (if (assoc-string "date" entry 'case-fold)
- "date"
- "year"))
- fields)))
- (-map (lambda (it)
- (cons (downcase (car it)) (cdr it)))
- (bibtex-completion-prepare-entry entry fields)))))
-
- (defun bibtex-completion-get-entry (entry-key)
- "Given a BibTeX key this function scans all bibliographies
- listed in `bibtex-completion-bibliography' and returns an alist of the
- record with that key. Fields from crossreferenced entries are
- appended to the requested entry."
- (let* ((entry (bibtex-completion-get-entry1 entry-key))
- (crossref (bibtex-completion-get-value "crossref" entry))
- (crossref (when crossref (bibtex-completion-get-entry1 crossref))))
- (bibtex-completion-remove-duplicated-fields (append entry crossref))))
-
- (defun bibtex-completion-get-entry1 (entry-key &optional do-not-find-pdf)
- (let ((bib (bibtex-completion-normalize-bibliography 'bibtex)))
- (with-temp-buffer
- (mapc #'insert-file-contents bib)
- (goto-char (point-min))
- (if (re-search-forward (concat "^[ \t]*@\\(" parsebib--bibtex-identifier
- "\\)[[:space:]]*[\(\{][[:space:]]*"
- (regexp-quote entry-key) "[[:space:]]*,")
- nil t)
- (let ((entry-type (match-string 1)))
- (reverse (bibtex-completion-prepare-entry
- (parsebib-read-entry entry-type (point) bibtex-completion-string-hash-table) nil do-not-find-pdf)))
- (progn
- (display-warning :warning (concat "Bibtex-completion couldn't find entry with key \"" entry-key "\"."))
- nil)))))
-
- (defun bibtex-completion-find-pdf-in-field (key-or-entry)
- "Returns the path of the PDF specified in the field
- `bibtex-completion-pdf-field' if that file exists. Returns nil if no
- file is specified, or if the specified file does not exist, or if
- `bibtex-completion-pdf-field' is nil."
- (when bibtex-completion-pdf-field
- (let* ((entry (if (stringp key-or-entry)
- (bibtex-completion-get-entry1 key-or-entry t)
- key-or-entry))
- (value (bibtex-completion-get-value bibtex-completion-pdf-field entry)))
- (cond
- ((not value) nil) ; Field not defined.
- ((f-file? value) (list value)) ; A bare full path was found.
- ((-any 'f-file? (--map (f-join it (f-filename value)) (-flatten bibtex-completion-library-path))) (-filter 'f-file? (--map (f-join it (f-filename value)) (-flatten bibtex-completion-library-path))))
- (t ; Zotero/Mendeley/JabRef format:
- (let ((value (replace-regexp-in-string "\\([^\\]\\);" "\\1\^^" value)))
- (cl-loop ; Looping over the files:
- for record in (s-split "\^^" value)
- ; Replace unescaped colons by field separator:
- for record = (replace-regexp-in-string "\\([^\\]\\|^\\):" "\\1\^_" record)
- ; Unescape stuff:
- for record = (replace-regexp-in-string "\\\\\\(.\\)" "\\1" record)
- ; Now we can safely split:
- for record = (s-split "\^_" record)
- for file-name = (nth 0 record)
- for path = (or (nth 1 record) "")
- for paths = (if (s-match "^[A-Z]:" path)
- (list path) ; Absolute Windows path
- ; Something else:
- (append
- (list
- path
- file-name
- (f-join (f-root) path) ; Mendeley #105
- (f-join (f-root) path file-name)) ; Mendeley #105
- (--map (f-join it path)
- (-flatten bibtex-completion-library-path)) ; Jabref #100
- (--map (f-join it path file-name)
- (-flatten bibtex-completion-library-path)))) ; Jabref #100
- for result = (-first (lambda (path)
- (if (and (not (s-blank-str? path))
- (f-exists? path))
- path nil)) paths)
- if result collect result)))))))
-
-
- (defun bibtex-completion-find-pdf-in-library (key-or-entry &optional find-additional)
- "Searches the directories in `bibtex-completion-library-path'
- for a PDF whose name is composed of the BibTeX key plus
- `bibtex-completion-pdf-extension'. The path of the first matching
- PDF is returned.
-
- If FIND-ADDITIONAL is non-nil, the paths of all PDFs whose name
- starts with the BibTeX key and ends with
- `bibtex-completion-pdf-extension' are returned instead."
- (let* ((key (if (stringp key-or-entry)
- key-or-entry
- (bibtex-completion-get-value "=key=" key-or-entry)))
- (main-pdf (cl-loop
- for dir in (-flatten bibtex-completion-library-path)
- append (cl-loop
- for ext in (-flatten bibtex-completion-pdf-extension)
- collect (f-join dir (s-concat key ext))))))
- (if find-additional
- (sort ; move main pdf on top of the list if needed
- (cl-loop
- for dir in (-flatten bibtex-completion-library-path)
- append (directory-files dir t
- (s-concat "^" (regexp-quote key)
- ".*\\("
- (mapconcat 'regexp-quote
- (-flatten bibtex-completion-pdf-extension)
- "\\|")
- "\\)$")))
- (lambda (x y)
- (and (member x main-pdf)
- (not (member y main-pdf)))))
- (-flatten (-first 'f-file? main-pdf)))))
-
- (defun bibtex-completion-find-pdf (key-or-entry &optional find-additional)
- "Returns the path of the PDF associated with the specified
- entry. This is either the path(s) specified in the field
- `bibtex-completion-pdf-field' or, if that does not exist, the
- first PDF in any of the directories in
- `bibtex-completion-library-path' whose name is composed of the
- the BibTeX key plus `bibtex-completion-pdf-extension' (or if
- FIND-ADDITIONAL is non-nil, all PDFs in
- `bibtex-completion-library-path' whose name starts with the
- BibTeX key and ends with `bibtex-completion-pdf-extension').
- Returns nil if no PDF is found."
- (or (bibtex-completion-find-pdf-in-field key-or-entry)
- (bibtex-completion-find-pdf-in-library key-or-entry find-additional)))
-
- (defun bibtex-completion-prepare-entry (entry &optional fields do-not-find-pdf)
- "Prepare ENTRY for display.
- ENTRY is an alist representing an entry as returned by
- parsebib-read-entry. All the fields not in FIELDS are removed
- from ENTRY, with the exception of the \"=type=\" and \"=key=\"
- fields. If FIELDS is empty, all fields are kept. Also add a
- =has-pdf= and/or =has-note= field, if they exist for ENTRY. If
- DO-NOT-FIND-PDF is non-nil, this function does not attempt to
- find a PDF file."
- (when entry ; entry may be nil, in which case just return nil
- (let* ((fields (when fields (append fields (list "=type=" "=key=" "=has-pdf=" "=has-note="))))
- ; Check for PDF:
- (entry (if (and (not do-not-find-pdf) (bibtex-completion-find-pdf entry))
- (cons (cons "=has-pdf=" bibtex-completion-pdf-symbol) entry)
- entry))
- (entry-key (cdr (assoc "=key=" entry)))
- ; Check for notes:
- (entry (if (or
- ;; One note file per entry:
- (and bibtex-completion-notes-path
- (f-directory? bibtex-completion-notes-path)
- (f-file? (f-join bibtex-completion-notes-path
- (s-concat entry-key
- bibtex-completion-notes-extension))))
- ;; All notes in one file:
- (and bibtex-completion-notes-path
- (f-file? bibtex-completion-notes-path)
- (member entry-key bibtex-completion-cached-notes-keys)))
- (cons (cons "=has-note=" bibtex-completion-notes-symbol) entry)
- entry))
- ; Remove unwanted fields:
- (entry (if fields
- (--filter (member-ignore-case (car it) fields) entry)
- entry)))
- ;; Normalize case of entry type:
- (setcdr (assoc "=type=" entry) (downcase (cdr (assoc "=type=" entry))))
- ;; Remove duplicated fields:
- (bibtex-completion-remove-duplicated-fields entry))))
-
- (defun bibtex-completion-remove-duplicated-fields (entry)
- "Remove duplicated fields from ENTRY."
- (cl-remove-duplicates entry
- :test (lambda (x y) (string= (s-downcase x) (s-downcase y)))
- :key 'car :from-end t))
-
- (defun bibtex-completion-format-entry (entry width)
- "Formats a BibTeX ENTRY for display in results list. WIDTH is
- the width of the results list. The display format is governed by
- the variable `bibtex-completion-display-formats'."
- (let* ((format
- (or (assoc-string (bibtex-completion-get-value "=type=" entry)
- bibtex-completion-display-formats-internal
- 'case-fold)
- (assoc t bibtex-completion-display-formats-internal)))
- (format-string (cadr format)))
- (s-format
- format-string
- (lambda (field)
- (let* ((field (split-string field ":"))
- (field-name (car field))
- (field-width (cadr field))
- (field-value (bibtex-completion-get-value field-name entry)))
- (when (and (string= field-name "author")
- (not field-value))
- (setq field-value (bibtex-completion-get-value "editor" entry)))
- (when (and (string= field-name "year")
- (not field-value))
- (setq field-value (car (split-string (bibtex-completion-get-value "date" entry "") "-"))))
- (setq field-value (bibtex-completion-clean-string (or field-value " ")))
- (when (member field-name '("author" "editor"))
- (setq field-value (bibtex-completion-shorten-authors field-value)))
- (if (not field-width)
- field-value
- (setq field-width (string-to-number field-width))
- (truncate-string-to-width
- field-value
- (if (> field-width 0)
- field-width
- (- width (cddr format)))
- 0 ?\s)))))))
-
- (defun bibtex-completion-clean-string (s)
- "Removes quoting and superfluous white space from BibTeX field
- values."
- (if s (replace-regexp-in-string "[\n\t ]+" " "
- (replace-regexp-in-string "[\"{}]+" "" s))
- nil))
-
- (defun bibtex-completion-shorten-authors (authors)
- "Returns a comma-separated list of the surnames in authors."
- (if authors
- (cl-loop for a in (s-split " and " authors)
- for p = (s-split "," a t)
- for sep = "" then ", "
- concat sep
- if (eq 1 (length p))
- concat (-last-item (s-split " +" (car p) t))
- else
- concat (car p))
- nil))
-
- (defun bibtex-completion-open-pdf (keys &optional fallback-action)
- "Open the PDFs associated with the marked entries using the
- function specified in `bibtex-completion-pdf-open-function'.
- If multiple PDFs are found for an entry, ask for the one to
- open using `completion-read'. If FALLBACK-ACTION is non-nil, it is called in
- case no PDF is found."
- (dolist (key keys)
- (let ((pdf (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs)))
- (cond
- ((> (length pdf) 1)
- (let* ((pdf (f-uniquify-alist pdf))
- (choice (completing-read "File to open: " (mapcar 'cdr pdf) nil t))
- (file (car (rassoc choice pdf))))
- (funcall bibtex-completion-pdf-open-function file)))
- (pdf
- (funcall bibtex-completion-pdf-open-function (car pdf)))
- (fallback-action
- (funcall fallback-action (list key)))
- (t
- (message "No PDF(s) found for this entry: %s"
- key))))))
-
- (defun bibtex-completion-open-url-or-doi (keys)
- "Open the associated URL or DOI in a browser."
- (dolist (key keys)
- (let* ((entry (bibtex-completion-get-entry key))
- (url (bibtex-completion-get-value "url" entry))
- (doi (bibtex-completion-get-value "doi" entry))
- (browse-url-browser-function
- (or bibtex-completion-browser-function
- browse-url-browser-function)))
- (if url
- (browse-url url)
- (if doi (browse-url
- (s-concat "http://dx.doi.org/" doi))
- (message "No URL or DOI found for this entry: %s"
- key))))))
-
- (defun bibtex-completion-open-any (keys)
- "Open the PDFs associated with the marked entries using the
- function specified in `bibtex-completion-pdf-open-function'.
- If multiple PDFs are found for an entry, ask for the one to
- open using `completion-read'. If no PDF is found, try to open a URL
- or DOI in the browser instead."
- (bibtex-completion-open-pdf keys 'bibtex-completion-open-url-or-doi))
-
- (defun bibtex-completion-format-citation-default (keys)
- "Default formatter for keys, separates multiple keys with commas."
- (s-join ", " keys))
-
- (defvar bibtex-completion-cite-command-history nil
- "History list for LaTeX citation commands.")
-
- (defun bibtex-completion-format-citation-cite (keys)
- "Formatter for LaTeX citation commands. Prompts for the command
- and for arguments if the commands can take any. If point is
- inside or just after a citation command, only adds KEYS to it."
- (let (macro)
- (cond
- ((and (require 'reftex-parse nil t)
- (setq macro (reftex-what-macro 1))
- (stringp (car macro))
- (string-match "\\`\\\\cite\\|cite\\'" (car macro)))
- ;; We are inside a cite macro. Insert key at point, with appropriate delimiters.
- (delete-horizontal-space)
- (concat (pcase (preceding-char)
- (?\{ "")
- (?, " ")
- (_ ", "))
- (s-join ", " keys)
- (if (member (following-char) '(?\} ?,))
- ""
- ", ")))
- ((and (equal (preceding-char) ?\})
- (require 'reftex-parse nil t)
- (save-excursion
- (forward-char -1)
- (setq macro (reftex-what-macro 1)))
- (stringp (car macro))
- (string-match "\\`\\\\cite\\|cite\\'" (car macro)))
- ;; We are right after a cite macro. Append key and leave point at the end.
- (delete-char -1)
- (delete-horizontal-space t)
- (concat (pcase (preceding-char)
- (?\{ "")
- (?, " ")
- (_ ", "))
- (s-join ", " keys)
- "}"))
- (t
- ;; We are not inside or right after a cite macro. Insert a full citation.
- (let* ((initial (when bibtex-completion-cite-default-as-initial-input
- bibtex-completion-cite-default-command))
- (default (unless bibtex-completion-cite-default-as-initial-input
- bibtex-completion-cite-default-command))
- (default-info (if default (format " (default \"%s\")" default) ""))
- (cite-command (completing-read
- (format "Cite command%s: " default-info)
- bibtex-completion-cite-commands nil nil initial
- 'bibtex-completion-cite-command-history default nil)))
- (if (member cite-command '("nocite" "supercite")) ; These don't want arguments.
- (format "\\%s{%s}" cite-command (s-join ", " keys))
- (let ((prenote (if bibtex-completion-cite-prompt-for-optional-arguments
- (read-from-minibuffer "Prenote: ")
- ""))
- (postnote (if bibtex-completion-cite-prompt-for-optional-arguments
- (read-from-minibuffer "Postnote: ")
- "")))
- (cond ((not (string= "" prenote))
- (format "\\%s[%s][%s]{%s}" cite-command prenote postnote (s-join ", " keys)))
- ((not (string= "" postnote))
- (format "\\%s[%s]{%s}" cite-command postnote (s-join ", " keys)))
- (t
- (format "\\%s{%s}" cite-command (s-join ", " keys)))))))))))
-
- (defun bibtex-completion-format-citation-pandoc-citeproc (keys)
- "Formatter for pandoc-citeproc citations."
- (let* ((prenote (if bibtex-completion-cite-prompt-for-optional-arguments (read-from-minibuffer "Prenote: ") ""))
- (postnote (if bibtex-completion-cite-prompt-for-optional-arguments (read-from-minibuffer "Postnote: ") ""))
- (prenote (if (string= "" prenote) "" (concat prenote " ")))
- (postnote (if (string= "" postnote) "" (concat ", " postnote))))
- (format "[%s%s%s]" prenote (s-join "; " (--map (concat "@" it) keys)) postnote)))
-
- (defun bibtex-completion-format-citation-ebib (keys)
- "Formatter for ebib references."
- (s-join ", "
- (--map (format "ebib:%s" it) keys)))
-
- (defun bibtex-completion-format-citation-org-link-to-PDF (keys)
- "Formatter for org-links to PDF. Uses first matching PDF if
- several are available. Entries for which no PDF is available are
- omitted."
- (s-join ", " (cl-loop
- for key in keys
- for pdfs = (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs)
- append (--map (format "[[%s][%s]]" it key) pdfs))))
-
- (defun bibtex-completion-format-citation-org-apa-link-to-PDF (keys)
- "Formatter for org-links to PDF. Link text loosely follows APA
- format. Uses first matching PDF if several are available."
- (s-join ", " (cl-loop
- for key in keys
- for entry = (bibtex-completion-get-entry key)
- for author = (bibtex-completion-shorten-authors
- (or (bibtex-completion-get-value "author" entry)
- (bibtex-completion-get-value "editor" entry)))
- for year = (or (bibtex-completion-get-value "year" entry)
- (car (split-string (bibtex-completion-get-value "date" entry "") "-")))
- for pdf = (car (bibtex-completion-find-pdf key))
- if pdf
- collect (format "[[file:%s][%s (%s)]]" pdf author year)
- else
- collect (format "%s (%s)" author year))))
-
- ;; When you want to create a todo list about reading, I think using
- ;; PDF's title is more intuitive.
- (defun bibtex-completion-format-citation-org-title-link-to-PDF (keys)
- "Formatter for org-links to PDF. Link text follows file title format.
- Uses first matching PDF if several are available."
- (s-join ", " (cl-loop
- for key in keys
- for entry = (bibtex-completion-get-entry key)
- for title = (bibtex-completion-get-value "title" entry)
- for pdf = (or (car (bibtex-completion-find-pdf key))
- (bibtex-completion-get-value "url" entry))
- if pdf
- collect (format "[[file:%s][%s]]" pdf title)
- else
- collect (format "%s" title))))
-
- (defun bibtex-completion-insert-citation (keys)
- "Insert citation at point. The format depends on
- `bibtex-completion-format-citation-functions'."
- (let ((format-function
- (cdr (or (assoc major-mode bibtex-completion-format-citation-functions)
- (assoc 'default bibtex-completion-format-citation-functions)))))
- (insert
- (funcall format-function keys))))
-
- (defun bibtex-completion-insert-reference (keys)
- "Insert a reference for each selected entry."
- (let* ((refs (--map
- (s-word-wrap fill-column
- (concat "\n- " (bibtex-completion-apa-format-reference it)))
- keys)))
- (insert "\n" (s-join "\n" refs) "\n")))
-
- (defun bibtex-completion-apa-format-reference (key)
- "Returns a plain text reference in APA format for the
- publication specified by KEY."
- (let*
- ((entry (bibtex-completion-get-entry key))
- (ref (pcase (downcase (bibtex-completion-get-value "=type=" entry))
- ("article"
- (s-format
- "${author} (${year}). ${title}. ${journal}, ${volume}(${number}), ${pages}.${doi}"
- 'bibtex-completion-apa-get-value entry))
- ("inproceedings"
- (s-format
- "${author} (${year}). ${title}. In ${editor}, ${booktitle} (pp. ${pages}). ${address}: ${publisher}."
- 'bibtex-completion-apa-get-value entry))
- ("book"
- (s-format
- "${author} (${year}). ${title}. ${address}: ${publisher}."
- 'bibtex-completion-apa-get-value entry))
- ("phdthesis"
- (s-format
- "${author} (${year}). ${title} (Doctoral dissertation). ${school}, ${address}."
- 'bibtex-completion-apa-get-value entry))
- ("inbook"
- (s-format
- "${author} (${year}). ${title}. In ${editor} (Eds.), ${booktitle} (pp. ${pages}). ${address}: ${publisher}."
- 'bibtex-completion-apa-get-value entry))
- ("incollection"
- (s-format
- "${author} (${year}). ${title}. In ${editor} (Eds.), ${booktitle} (pp. ${pages}). ${address}: ${publisher}."
- 'bibtex-completion-apa-get-value entry))
- ("proceedings"
- (s-format
- "${editor} (Eds.). (${year}). ${booktitle}. ${address}: ${publisher}."
- 'bibtex-completion-apa-get-value entry))
- ("unpublished"
- (s-format
- "${author} (${year}). ${title}. Unpublished manuscript."
- 'bibtex-completion-apa-get-value entry))
- (_
- (s-format
- "${author} (${year}). ${title}."
- 'bibtex-completion-apa-get-value entry)))))
- (replace-regexp-in-string "\\([.?!]\\)\\." "\\1" ref))) ; Avoid sequences of punctuation marks.
-
- (defun bibtex-completion-apa-get-value (field entry &optional default)
- "Return FIELD or ENTRY formatted following the APA
- guidelines. Return DEFAULT if FIELD is not present in ENTRY."
- ;; Virtual fields:
- (cond
- ((string= field "author-or-editor")
- (let ((value (bibtex-completion-get-value "author" entry)))
- (if value
- (bibtex-completion-apa-format-authors value)
- (bibtex-completion-apa-format-editors
- (bibtex-completion-get-value "editor" entry)))))
- ((string= field "author-abbrev")
- (let ((value (bibtex-completion-get-value "author" entry)))
- (bibtex-completion-apa-format-authors-abbrev value)))
- (t
- ;; Real fields:
- (let ((value (bibtex-completion-get-value field entry)))
- (if value
- (pcase field
- ;; https://owl.english.purdue.edu/owl/resource/560/06/
- ("author" (bibtex-completion-apa-format-authors value))
- ("editor" (bibtex-completion-apa-format-editors value))
- ;; When referring to books, chapters, articles, or Web pages,
- ;; capitalize only the first letter of the first word of a
- ;; title and subtitle, the first word after a colon or a dash
- ;; in the title, and proper nouns. Do not capitalize the first
- ;; letter of the second word in a hyphenated compound word.
- ("title" (replace-regexp-in-string ; remove braces
- "[{}]"
- ""
- (replace-regexp-in-string ; remove macros
- "\\\\[[:alpha:]]+{"
- ""
- (replace-regexp-in-string ; upcase initial letter
- "^[[:alpha:]]"
- 'upcase
- (replace-regexp-in-string ; preserve stuff in braces from being downcased
- "\\(^[^{]*{\\)\\|\\(}[^{]*{\\)\\|\\(}.*$\\)\\|\\(^[^{}]*$\\)"
- (lambda (x) (downcase (s-replace "\\" "\\\\" x)))
- value)))))
- ("booktitle" value)
- ;; Maintain the punctuation and capitalization that is used by
- ;; the journal in its title.
- ("pages" (s-join "–" (s-split "[^0-9]+" value t)))
- ("doi" (s-concat " http://dx.doi.org/" value))
- ("year" (or value
- (car (split-string (bibtex-completion-get-value "date" entry "") "-"))))
- (_ value))
- "")))))
-
- (defun bibtex-completion-apa-format-authors (value)
- (cl-loop for a in (s-split " and " value t)
- if (s-index-of "{" a)
- collect
- (replace-regexp-in-string "[{}]" "" a)
- into authors
- else if (s-index-of "," a)
- collect
- (let ((p (s-split " *, *" a t)))
- (concat
- (car p) ", "
- (s-join " " (-map (lambda (it) (concat (s-left 1 it) "."))
- (s-split " " (cadr p))))))
- into authors
- else
- collect
- (let ((p (s-split " " a t)))
- (concat
- (-last-item p) ", "
- (s-join " " (-map (lambda (it) (concat (s-left 1 it) "."))
- (-butlast p)))))
- into authors
- finally return
- (let ((l (length authors)))
- (cond
- ((= l 1) (car authors))
- ((< l 8) (concat (s-join ", " (-butlast authors))
- ", & " (-last-item authors)))
- (t (concat (s-join ", " (-slice authors 0 7)) ", …"))))))
-
- (defun bibtex-completion-apa-format-authors-abbrev (value)
- (cl-loop for a in (s-split " and " value t)
- if (s-index-of "{" a)
- collect
- (replace-regexp-in-string "[{}]" "" a)
- into authors
- else if (s-index-of "," a)
- collect
- (let ((p (s-split " *, *" a t)))
- (concat
- (car p) ", "
- (s-join " " (-map (lambda (it) (concat (s-left 1 it) "."))
- (s-split " " (cadr p))))))
- into authors
- else
- collect
- (let ((p (s-split " " a t)))
- (concat
- (-last-item p) ", "
- (s-join " " (-map (lambda (it) (concat (s-left 1 it) "."))
- (-butlast p)))))
- into authors
- finally return
- (let ((l (length authors)))
- (cond
- ((= l 1) (car authors))
- ((= l 2) (concat (s-join " & " authors)))
- (t (format "%s et al." (car authors)))))))
-
- (defun bibtex-completion-apa-format-editors (value)
- (cl-loop for a in (s-split " and " value t)
- if (s-index-of "," a)
- collect
- (let ((p (s-split " *, *" a t)))
- (concat
- (s-join " " (-map (lambda (it) (concat (s-left 1 it) "."))
- (s-split " " (cadr p))))
- " " (car p)))
- into authors
- else
- collect
- (let ((p (s-split " " a t)))
- (concat
- (s-join " " (-map (lambda (it) (concat (s-left 1 it) "."))
- (-butlast p)))
- " " (-last-item p)))
- into authors
- finally return
- (let ((l (length authors)))
- (cond
- ((= l 1) (car authors))
- ((< l 8) (concat (s-join ", " (-butlast authors))
- ", & " (-last-item authors)))
- (t (concat (s-join ", " authors) ", ..."))))))
-
- (defun bibtex-completion-get-value (field entry &optional default)
- "Return the requested value or `default' if the value is not
- defined. Surrounding curly braces are stripped."
- (let ((value (cdr (assoc-string field entry 'case-fold))))
- (if value
- (replace-regexp-in-string
- "\\(^[[:space:]]*[\"{][[:space:]]*\\)\\|\\([[:space:]]*[\"}][[:space:]]*$\\)"
- ""
- (s-collapse-whitespace value))
- default)))
-
- (defun bibtex-completion-insert-key (keys)
- "Insert BibTeX key at point."
- (insert
- (funcall 'bibtex-completion-format-citation-default keys)))
-
- (defun bibtex-completion-insert-bibtex (keys)
- "Insert BibTeX key at point."
- (insert (s-join "\n" (--map (bibtex-completion-make-bibtex it) keys))))
-
- (defun bibtex-completion-make-bibtex (key)
- (let* ((entry (bibtex-completion-get-entry key))
- (entry-type (bibtex-completion-get-value "=type=" entry)))
- (format "@%s{%s,\n%s}\n"
- entry-type key
- (cl-loop
- for field in entry
- for name = (car field)
- for value = (cdr field)
- unless (member name
- (append (-map (lambda (it) (if (symbolp it) (symbol-name it) it))
- bibtex-completion-no-export-fields)
- '("=type=" "=key=" "=has-pdf=" "=has-note=" "crossref")))
- concat
- (format " %s = {%s},\n" name value)))))
-
- (defun bibtex-completion-add-PDF-attachment (keys)
- "Attach the PDFs of the selected entries where available."
- (dolist (key keys)
- (let ((pdf (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs)))
- (if pdf
- (mapc 'mml-attach-file pdf)
- (message "No PDF(s) found for this entry: %s"
- key)))))
-
- (define-minor-mode bibtex-completion-notes-mode
- "Minor mode for managing notes."
- :keymap (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c C-c") 'bibtex-completion-exit-notes-buffer)
- (define-key map (kbd "C-c C-w") 'org-refile)
- map)
- (setq-local
- header-line-format
- (substitute-command-keys
- " Finish \\[bibtex-completion-exit-notes-buffer], refile \\[org-refile]")))
-
- ;; Define global minor mode. This is needed to the toggle minor mode.
- (define-globalized-minor-mode bibtex-completion-notes-global-mode bibtex-completion-notes-mode bibtex-completion-notes-mode)
-
- (defun bibtex-completion-exit-notes-buffer ()
- "Exit notes buffer and delete its window.
- This will also disable `bibtex-completion-notes-mode' and remove the header
- line."
- (interactive)
- (widen)
- (bibtex-completion-notes-global-mode -1)
- (setq-local
- header-line-format nil)
- (save-buffer)
- (let ((window (get-buffer-window (get-file-buffer bibtex-completion-notes-path))))
- (if (and window (not (one-window-p window)))
- (delete-window window)
- (switch-to-buffer (other-buffer)))))
-
- (defun bibtex-completion-edit-notes (keys)
- "Open the notes associated with the selected entries using `find-file'."
- (dolist (key keys)
- (let* ((entry (bibtex-completion-get-entry key))
- (year (or (bibtex-completion-get-value "year" entry)
- (car (split-string (bibtex-completion-get-value "date" entry "") "-"))))
- (entry (push (cons "year" year) entry)))
- (if (and bibtex-completion-notes-path
- (f-directory? bibtex-completion-notes-path))
- ; One notes file per publication:
- (let* ((path (f-join bibtex-completion-notes-path
- (s-concat key bibtex-completion-notes-extension))))
- (find-file path)
- (unless (f-exists? path)
- (insert (s-format bibtex-completion-notes-template-multiple-files
- 'bibtex-completion-apa-get-value
- entry))))
- ; One file for all notes:
- (unless (and buffer-file-name
- (f-same? bibtex-completion-notes-path buffer-file-name))
- (find-file-other-window bibtex-completion-notes-path))
- (widen)
- (outline-show-all)
- (goto-char (point-min))
- (if (re-search-forward (format bibtex-completion-notes-key-pattern (regexp-quote key)) nil t)
- ; Existing entry found:
- (when (eq major-mode 'org-mode)
- (org-narrow-to-subtree)
- (re-search-backward "^\*+ " nil t)
- (org-cycle-hide-drawers nil)
- (bibtex-completion-notes-mode 1))
- ; Create a new entry:
- (goto-char (point-max))
- (save-excursion (insert (s-format bibtex-completion-notes-template-one-file
- 'bibtex-completion-apa-get-value
- entry)))
- (re-search-forward "^*+ " nil t))
- (when (eq major-mode 'org-mode)
- (org-narrow-to-subtree)
- (re-search-backward "^\*+ " nil t)
- (org-cycle-hide-drawers nil)
- (goto-char (point-max))
- (bibtex-completion-notes-mode 1))))))
-
- (defun bibtex-completion-buffer-visiting (file)
- (or (get-file-buffer file)
- (find-buffer-visiting file)))
-
- (defun bibtex-completion-show-entry (keys)
- "Show the first selected entry in the BibTeX file."
- (catch 'break
- (dolist (bib-file (bibtex-completion-normalize-bibliography 'main))
- (let ((key (car keys))
- (buf (bibtex-completion-buffer-visiting bib-file)))
- (find-file bib-file)
- (widen)
- (if (eq major-mode 'org-mode)
- (let* ((prop (if (boundp 'org-bibtex-key-property)
- org-bibtex-key-property
- "CUSTOM_ID"))
- (match (org-find-property prop key)))
- (when match
- (goto-char match)
- (org-show-entry)
- (throw 'break t)))
- (goto-char (point-min))
- (when (re-search-forward
- (concat "^@\\(" parsebib--bibtex-identifier
- "\\)[[:space:]]*[\(\{][[:space:]]*"
- (regexp-quote key) "[[:space:]]*,") nil t)
- (throw 'break t)))
- (unless buf
- (kill-buffer))))))
-
- (defun bibtex-completion-add-pdf-to-library (keys)
- "Add a PDF to the library for the first selected entry. The PDF
- can be added either from an open buffer, a file, or a URL."
- (let* ((key (car keys))
- (source (char-to-string
- (read-char-choice "Add pdf from [b]uffer, [f]ile, or [u]rl? " '(?b ?f ?u))))
- (buffer (when (string= source "b")
- (read-buffer-to-switch "Add pdf buffer: ")))
- (file (when (string= source "f")
- (expand-file-name (read-file-name "Add pdf file: " nil nil t))))
- (url (when (string= source "u")
- (read-string "Add pdf URL: ")))
- (path (-flatten (list bibtex-completion-library-path)))
- (path (if (cdr path)
- (completing-read "Add pdf to: " path nil t)
- (car path)))
- (pdf (expand-file-name (completing-read "Rename pdf to: "
- (--map (s-concat key it)
- (-flatten bibtex-completion-pdf-extension))
- nil nil key)
- path)))
- (cond
- (buffer
- (with-current-buffer buffer
- (write-file pdf t)))
- (file
- (copy-file file pdf 1))
- (url
- (url-copy-file url pdf 1)))))
-
- (defun bibtex-completion-fallback-action (url-or-function search-expression)
- (let ((browse-url-browser-function
- (or bibtex-completion-browser-function
- browse-url-browser-function)))
- (cond
- ((stringp url-or-function)
- (browse-url (format url-or-function (url-hexify-string search-expression))))
- ((functionp url-or-function)
- (funcall url-or-function search-expression))
- (t (error "Don't know how to interpret this: %s" url-or-function)))))
-
- (defun bibtex-completion-fallback-candidates ()
- "Compile list of fallback options. These consist of the online
- resources defined in `bibtex-completion-fallback-options' plus
- one entry for each bibliography file that will open that file for
- editing."
- (let ((bib-files (bibtex-completion-normalize-bibliography 'main)))
- (-concat
- (--map (cons (s-concat "Create new entry in " (f-filename it))
- `(lambda (_search-expression) (find-file ,it) (goto-char (point-max)) (newline)))
- bib-files)
- bibtex-completion-fallback-options)))
-
- (defun bibtex-completion-find-local-bibliography ()
- "Return a list of BibTeX files associated with the current
- file. If the current file is a BibTeX file, return this
- file. Otherwise, try to use `reftex' to find the associated
- BibTeX files. If this fails, return nil."
- (or (and (buffer-file-name)
- (string= (or (f-ext (buffer-file-name)) "") "bib")
- (list (buffer-file-name)))
- (and (buffer-file-name)
- (require 'reftex-cite nil t)
- (ignore-errors (reftex-get-bibfile-list)))))
-
- (defun bibtex-completion-key-at-point ()
- "Return the key of the BibTeX entry at point. If the current
- file is a BibTeX file, return the key of the entry at
- point. Otherwise, try to use `reftex' to check whether point is
- at a citation macro, and if so return the key at
- point. Otherwise, if the current file is an org-mode file, return
- the value of `org-bibtex-key-property' (or
- default to \"CUSTOM_ID\"). Otherwise, return nil."
- (or (and (eq major-mode 'bibtex-mode)
- (save-excursion
- (bibtex-beginning-of-entry)
- (and (looking-at bibtex-entry-maybe-empty-head)
- (bibtex-key-in-head))))
- (and (require 'reftex-parse nil t)
- (save-excursion
- (skip-chars-backward "[:space:],;}")
- (let ((macro (reftex-what-macro 1)))
- (and (stringp (car macro))
- (string-match "\\`\\\\cite\\|cite\\'" (car macro))
- ;; allow '_' in citekeys
- (let ((temp-syn-table (make-syntax-table)))
- (modify-syntax-entry ?_ "_" temp-syn-table)
- (with-syntax-table temp-syn-table
- (thing-at-point 'symbol)))))))
- (and (eq major-mode 'org-mode)
- (let (key)
- (and (setq key (org-entry-get nil
- (if (boundp 'org-bibtex-key-property)
- org-bibtex-key-property
- "CUSTOM_ID")
- t))
- ;; KEY may be the empty string the the property is
- ;; present but has no value
- (> (length key) 0)
- key)))))
-
- (provide 'bibtex-completion)
-
- ;; Local Variables:
- ;; byte-compile-warnings: (not cl-functions obsolete)
- ;; coding: utf-8
- ;; indent-tabs-mode: nil
- ;; End:
-
- ;;; bibtex-completion.el ends here
|