;;; org-ref-core.el --- citations, cross-references and bibliographies in org-mode
|
|
|
|
;; Copyright(C) 2014-2017 John Kitchin
|
|
|
|
;; This file is not currently part of GNU Emacs.
|
|
|
|
;; 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 2, 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 ; see the file COPYING. If not, write to
|
|
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
;; Boston, MA 02111-1307, USA.
|
|
|
|
;;; Commentary:
|
|
;;
|
|
;; Lisp code to setup bibliography, cite, ref and label org-mode links.
|
|
;; Also sets up reftex and helm for org-mode citations. The links are
|
|
;; clickable and do things that are useful. You should really read
|
|
;; org-ref.org in this package for details.
|
|
;;
|
|
|
|
;;; Code:
|
|
(eval-when-compile
|
|
(require 'cl-lib))
|
|
(require 'dash)
|
|
(require 'f)
|
|
(require 'htmlize)
|
|
(require 's)
|
|
(require 'doi-utils)
|
|
|
|
(add-to-list 'load-path
|
|
(expand-file-name
|
|
"citeproc"
|
|
(file-name-directory (or load-file-name (buffer-file-name)))))
|
|
|
|
(add-to-list 'load-path
|
|
(expand-file-name
|
|
"citeproc/csl"
|
|
(file-name-directory (or load-file-name (buffer-file-name)))))
|
|
|
|
(require 'org-ref-bibtex)
|
|
(require 'org-ref-utils)
|
|
(require 'org-ref-glossary)
|
|
(require 'org)
|
|
(require 'org-element)
|
|
(require 'ox)
|
|
(require 'parsebib)
|
|
(require 'reftex-cite)
|
|
|
|
(defvar org-export-exclude-tags)
|
|
(defvar warning-suppress-types)
|
|
(declare-function bibtex-completion-get-entry "bibtex-completion")
|
|
(declare-function bibtex-completion-edit-notes "bibtex-completion")
|
|
|
|
|
|
;;* Custom variables
|
|
(defgroup org-ref nil
|
|
"Customization group for org-ref."
|
|
:tag "Org Ref"
|
|
:group 'org)
|
|
|
|
|
|
(defcustom org-ref-bibliography-notes
|
|
nil
|
|
"Filename where you will put all your notes about an entry in the default bibliography.
|
|
Used by backends that append all notes as entries in a single file.
|
|
|
|
See also `org-ref-notes-function'"
|
|
:type '(choice (const nil)
|
|
(file))
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-notes-directory
|
|
nil
|
|
"Directory where you will put all your notes about an entry in the default bibliography.
|
|
Used for backends that create a single file of notes per entry.
|
|
|
|
See also `org-ref-notes-function'."
|
|
:type 'directory
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-default-bibliography
|
|
nil
|
|
"List of bibtex files to search for.
|
|
You should use full-paths for each file. Note that you must
|
|
include a bibliography link in your document if you will be
|
|
exporting it to pdf; org-ref-default-bibliography is not
|
|
used by the LaTeX exporter."
|
|
:type '(repeat :tag "List of bibtex files" file)
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-pdf-directory
|
|
nil
|
|
"Directory where pdfs are stored by key.
|
|
Put a trailing / in the name."
|
|
:type '(choice directory (repeat directory))
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-default-citation-link
|
|
"cite"
|
|
"The default type of citation link to use."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-insert-cite-key
|
|
"C-c ]"
|
|
"Keyboard shortcut to insert a citation."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-completion-library
|
|
'org-ref-helm-bibtex
|
|
"Symbol for library to define completion functions.
|
|
The completion library should provide functions for
|
|
`org-ref-insert-link-function', `org-ref-insert-cite-function',
|
|
`org-ref-insert-label-function', `org-ref-insert-ref-function',
|
|
and `org-ref-cite-onclick-function', and set those variables to
|
|
the values of those functions."
|
|
:type 'symbol
|
|
:options '(org-ref-helm-bibtex ; completion with helm + helm-bibtex
|
|
org-ref-helm-cite ; completion with helm in org-ref
|
|
org-ref-ivy-cite ; completion with ivy
|
|
org-ref-reftex ; org-completion
|
|
)
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-insert-link-function
|
|
nil
|
|
"Generic function for inserting org-ref links.
|
|
The function should take a prefix arg.
|
|
No arg means insert a cite link
|
|
1 arg means insert a ref link
|
|
2 args means insert a label."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-insert-cite-function
|
|
nil
|
|
"Function to call to insert citation links.
|
|
This function should prompt for keys with completion, and insert
|
|
the citation link into the buffer."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-prefer-bracket-links nil
|
|
"If non-nil use bracketed links when inserting them."
|
|
:type 'boolean
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-cite-completion-function
|
|
nil
|
|
"Function to prompt for keys with completion."
|
|
:type '(choice (const nil)
|
|
(function))
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-insert-label-function
|
|
nil
|
|
"Function to call to insert label links.
|
|
This function should prompt for a label, and insert the label
|
|
link."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-insert-ref-function
|
|
nil
|
|
"Function to call to insert ref links.
|
|
This function should prompt for a label with completion, and
|
|
insert the ref link."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-cite-onclick-function
|
|
nil
|
|
"Function that runs when you click on a cite link.
|
|
The function must take one argument which is the path of the link
|
|
that was clicked on. This function is normally set by the
|
|
function in `org-ref-completion-library'."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
;; define key for inserting citations
|
|
(define-key org-mode-map
|
|
(kbd org-ref-insert-cite-key)
|
|
org-ref-insert-link-function)
|
|
|
|
|
|
(defcustom org-ref-cite-keymap
|
|
(let ((map (copy-keymap org-mouse-map)))
|
|
(define-key map (kbd "H-o") 'org-ref-cite-hydra/body)
|
|
(define-key map (kbd "H-b") 'org-ref-open-citation-at-point)
|
|
(define-key map (kbd "H-u") 'org-ref-open-url-at-point)
|
|
(define-key map (kbd "H-p") 'org-ref-open-pdf-at-point)
|
|
(define-key map (kbd "H-n") 'org-ref-open-notes-at-point)
|
|
(define-key map (kbd "H-r") 'org-ref-wos-related-at-point)
|
|
(define-key map (kbd "H-c") 'org-ref-wos-citing-at-point)
|
|
(define-key map (kbd "H-e") (lambda ()
|
|
"Email entry at point"
|
|
(interactive)
|
|
(org-ref-open-citation-at-point)
|
|
(org-ref-email-bibtex-entry)))
|
|
(define-key map (kbd "H-g") 'org-ref-google-scholar-at-point)
|
|
(define-key map (kbd "H-f") (lambda ()
|
|
(interactive)
|
|
(save-excursion
|
|
(org-ref-open-citation-at-point)
|
|
(kill-new
|
|
(org-ref-format-bibtex-entry-at-point)))))
|
|
(define-key map (kbd "H-w") (lambda ()
|
|
(interactive)
|
|
(kill-new (car (org-ref-get-bibtex-key-and-file)))))
|
|
(define-key map (kbd "H-W") (lambda ()
|
|
"Copy all the keys at point."
|
|
(interactive)
|
|
(kill-new (org-element-property :path (org-element-context)))))
|
|
(define-key map (kbd "H-y") (lambda ()
|
|
"Paste key at point. Assumes the first thing in the killring is a key."
|
|
(interactive)
|
|
(org-ref-insert-key-at-point (car kill-ring))))
|
|
|
|
;; Navigation keys
|
|
(define-key map (kbd "C-<left>") 'org-ref-previous-key)
|
|
(define-key map (kbd "C-<right>") 'org-ref-next-key)
|
|
|
|
;; rearrangement keys
|
|
(define-key map (kbd "S-<left>") (lambda () (interactive) (org-ref-swap-citation-link -1)))
|
|
(define-key map (kbd "S-<right>") (lambda () (interactive) (org-ref-swap-citation-link 1)))
|
|
(define-key map (kbd "S-<up>") 'org-ref-sort-citation-link)
|
|
(define-key map (kbd "<tab>") (lambda () (interactive)
|
|
(funcall org-ref-insert-cite-function)))
|
|
map)
|
|
"Keymap for cite links."
|
|
:type 'symbol
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-bibliography-entry-format
|
|
'(("article" . "%a, %t, <i>%j</i>, <b>%v(%n)</b>, %p (%y). <a href=\"%U\">link</a>. <a href=\"http://dx.doi.org/%D\">doi</a>.")
|
|
|
|
("book" . "%a, %t, %u (%y).")
|
|
("techreport" . "%a, %t, %i, %u (%y).")
|
|
("proceedings" . "%e, %t in %S, %u (%y).")
|
|
("inproceedings" . "%a, %t, %p, in %b, edited by %e, %u (%y)"))
|
|
"String to format an entry.
|
|
Just the reference, no numbering at the beginning, etc... see the
|
|
`org-ref-reftex-format-citation' docstring for the escape codes."
|
|
:type '(alist :key-type (string) :value-type (string))
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-note-title-format
|
|
"** TODO %y - %t
|
|
:PROPERTIES:
|
|
:Custom_ID: %k
|
|
:AUTHOR: %9a
|
|
:JOURNAL: %j
|
|
:YEAR: %y
|
|
:VOLUME: %v
|
|
:PAGES: %p
|
|
:DOI: %D
|
|
:URL: %U
|
|
:END:
|
|
|
|
"
|
|
"String to format the title and properties drawer of a note.
|
|
See the `org-ref-reftex-format-citation' docstring for the escape
|
|
codes."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-ref-html "<a class='org-ref-reference' href=\"#%s\">%s</a>"
|
|
"HTML code to represent a reference."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-notes-function #'org-ref-notes-function-one-file
|
|
"Function to open the notes for the bibtex key in a cite link at point.
|
|
|
|
The default behavior adds entries to a long file with headlines
|
|
for each entry. It also tries to be compatible with `org-bibtex'.
|
|
|
|
An alternative is `org-ref-notes-function-many-files'. Use that
|
|
if you prefer the `bibtex-completion' approach, which also
|
|
supports an additional method for storing notes. See
|
|
`bibtex-completion-notes-path' for more information. You may also
|
|
want to set `org-ref-notes-directory'."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-open-notes-function
|
|
(lambda ()
|
|
(org-show-entry)
|
|
(outline-show-branches)
|
|
(outline-show-children)
|
|
(org-cycle '(64))
|
|
(recenter-top-bottom 0))
|
|
"User-defined way to open a notes entry.
|
|
This is executed after the entry is found in
|
|
`org-ref-open-bibtex-notes', with the cursor at the beginning of
|
|
the headline. The default setting fully expands the notes, and
|
|
moves the headline to the top of the buffer."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-create-notes-hook
|
|
'((lambda ()
|
|
(org-narrow-to-subtree)
|
|
(insert (format "cite:%s\n" (org-entry-get (point) "Custom_ID")))))
|
|
"List of hook functions to run in the note entry after it is created.
|
|
The function takes no arguments. It could be used to insert links
|
|
to the citation, or pdf, etc..."
|
|
:type 'hook
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-open-pdf-function
|
|
'org-ref-open-pdf-at-point
|
|
"User-defined function to open a pdf from a link.
|
|
The function must get the key at point, and derive a path to the pdf
|
|
file, then open it. The default function is
|
|
`org-ref-open-pdf-at-point'."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-get-pdf-filename-function
|
|
'org-ref-get-pdf-filename
|
|
"User-defined function to get a filename from a bibtex key.
|
|
The function must take a key as an argument, and return the path
|
|
to the corresponding filename. The default is
|
|
`org-ref-get-pdf-filename'. Alternative values are
|
|
`org-ref-get-mendeley-filename' or
|
|
`org-ref-get-pdf-filename-helm-bibtex'."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-clean-bibtex-key-function
|
|
(lambda (key)
|
|
(replace-regexp-in-string ":" "" key))
|
|
"Function to modify a bibtex key.
|
|
The default behavior is to remove : from the key."
|
|
:type 'function
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-show-citation-on-enter t
|
|
"If non-nil show the citation summary.
|
|
Uses a hook function to display the message in the minibuffer."
|
|
:type 'boolean
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-natbib-types
|
|
'("citet" "citet*" "citep" "citep*"
|
|
"citealt" "citealt*" "citealp" "citealp*"
|
|
"citenum" "citetext"
|
|
"citeauthor" "citeauthor*"
|
|
"citeyear" "citeyear*" "citeyearpar"
|
|
"Citet" "Citep" "Citealt" "Citealp" "Citeauthor")
|
|
"natbib cite commands, http://tug.ctan.org/macros/latex/contrib/natbib/natnotes.pdf"
|
|
:type '(repeat :tag "List of citation types" string)
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-biblatex-types
|
|
'("Cite"
|
|
"parencite" "Parencite"
|
|
"footcite" "footcitetext"
|
|
"textcite" "Textcite"
|
|
"smartcite" "Smartcite"
|
|
"cite*" "parencite*" "supercite"
|
|
"autocite" "Autocite" "autocite*" "Autocite*"
|
|
"Citeauthor*"
|
|
"citetitle" "citetitle*"
|
|
"citedate" "citedate*"
|
|
"citeurl"
|
|
"fullcite" "footfullcite"
|
|
;; "volcite" "Volcite" cannot support the syntax
|
|
"notecite" "Notecite"
|
|
"pnotecite" "Pnotecite"
|
|
"fnotecite"
|
|
;; multicites. Very limited support for these.
|
|
"cites" "Cites" "parencites" "Parencites"
|
|
"footcites" "footcitetexts"
|
|
"smartcites" "Smartcites" "textcites" "Textcites"
|
|
"supercites" "autocites" "Autocites")
|
|
"biblatex commands
|
|
http://ctan.mirrorcatalogs.com/macros/latex/contrib/biblatex/doc/biblatex.pdf"
|
|
:type '(repeat :tag "List of citation types" string)
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-cite-types
|
|
(append
|
|
'("cite" "nocite") ;; the default latex cite commands
|
|
org-ref-natbib-types
|
|
org-ref-biblatex-types
|
|
;; for the bibentry package
|
|
'("bibentry"))
|
|
"List of citation types known in `org-ref'."
|
|
:type '(repeat :tag "List of citation types" string)
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-ref-types
|
|
'("ref" "eqref" "pageref" "nameref" "autoref" "cref" "Cref")
|
|
"List of ref link types."
|
|
:type '(repeat :tag "List of ref types" string)
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-default-ref-type "ref"
|
|
"Default ref link type to use when inserting ref links"
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-clean-bibtex-entry-hook
|
|
'(org-ref-bibtex-format-url-if-doi
|
|
orcb-key-comma
|
|
org-ref-replace-nonascii
|
|
orcb-&
|
|
orcb-%
|
|
org-ref-title-case-article
|
|
orcb-clean-year
|
|
orcb-key
|
|
orcb-clean-doi
|
|
orcb-clean-pages
|
|
orcb-check-journal
|
|
org-ref-sort-bibtex-entry
|
|
orcb-fix-spacing)
|
|
"Hook that is run in `org-ref-clean-bibtex-entry'.
|
|
The functions should have no arguments, and
|
|
operate on the bibtex entry at point. You can assume point starts
|
|
at the beginning of the entry. These functions are wrapped in
|
|
`save-restriction' and `save-excursion' so you do not need to
|
|
save the point position.
|
|
|
|
Org ref contains some functions that are not included by default
|
|
such as `orcb-clean-nil' or `orcb-clean-nil-opinionated' that
|
|
users may be interested in adding themselves."
|
|
:group 'org-ref
|
|
:type 'hook)
|
|
|
|
|
|
(defcustom org-ref-bibtex-sort-order
|
|
'(("article" . ("author" "title" "journal" "volume" "number" "pages" "year" "doi" "url"))
|
|
("inproceedings" . ("author" "title" "booktitle" "year" "volume" "number" "pages" "doi" "url"))
|
|
("book" . ("author" "title" "year" "publisher" "url")))
|
|
"A-list of bibtex entry fields and the order to sort an entry with.
|
|
\(entry-type . (list of fields). This is used in
|
|
`org-ref-sort-bibtex-entry'. Entry types not listed here will
|
|
have fields sorted alphabetically."
|
|
:type '(alist :key-type (string) :value-type (repeat string))
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-printbibliography-cmd "\\printbibliography"
|
|
"LaTeX command to print bibliography. Customize this to add options."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defvar org-ref-bibliography-files
|
|
nil
|
|
"Variable to hold bibliography files to be searched.")
|
|
|
|
|
|
(defcustom org-ref-show-broken-links t
|
|
"If non-nil show bad org-ref links in a warning face."
|
|
:type 'boolean
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-enable-colon-insert nil
|
|
"If non-nil enable colon to insert cites, labels, and ref links."
|
|
:type 'booleanp
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-label-use-font-lock t
|
|
"If non-nil use font-lock to find labels in the buffer.
|
|
If nil, each time you ask for labels the whole buffer will be
|
|
searched, which may be slower.")
|
|
|
|
|
|
(defun org-ref-colon-insert-link (arg)
|
|
"Function to run when : has a special meaning.
|
|
See `org-ref-enable-colon-insert'."
|
|
(interactive "P")
|
|
(insert ":")
|
|
(cond
|
|
;; cite links
|
|
((save-excursion
|
|
(backward-word 1)
|
|
(looking-at (regexp-opt org-ref-cite-types)))
|
|
(funcall org-ref-insert-cite-function))
|
|
((save-excursion
|
|
(backward-word 1)
|
|
(looking-at "label:"))
|
|
(funcall org-ref-insert-label-function))
|
|
((save-excursion
|
|
(backward-word 1)
|
|
(looking-at (regexp-opt org-ref-ref-types)))
|
|
(funcall org-ref-insert-ref-function))))
|
|
|
|
|
|
(when org-ref-enable-colon-insert
|
|
(define-key org-mode-map ":"
|
|
'(menu-item "maybe-cite" nil
|
|
:filter (lambda (&optional _)
|
|
(unless (org-in-src-block-p)
|
|
#'org-ref-colon-insert-link)))))
|
|
|
|
|
|
(defun org-ref-change-cite-type (new-type)
|
|
"Change the cite type to NEW-TYPE."
|
|
(interactive (list (completing-read "Type: " org-ref-cite-types)))
|
|
(let* ((cite-link (org-element-context))
|
|
(old-type (org-element-property :type cite-link))
|
|
(begin (org-element-property :begin cite-link))
|
|
(end (org-element-property :end cite-link))
|
|
(bracketp (eq 'bracket (org-element-property :format cite-link)))
|
|
(path (org-element-property :path cite-link))
|
|
(deltap (- (point) begin)))
|
|
;; note this does not respect brackets
|
|
(setf (buffer-substring begin end)
|
|
(concat
|
|
(if bracketp "[[" "")
|
|
new-type ":" path
|
|
(if bracketp "]]" "")))
|
|
;; try to preserve the character the point is on.
|
|
(goto-char (+ begin deltap (- (length new-type) (length old-type))))))
|
|
|
|
|
|
|
|
(defun org-ref-change-ref-type (new-type)
|
|
"Change the ref type to NEW-TYPE."
|
|
(interactive (list (completing-read "Type: " org-ref-ref-types)))
|
|
(let* ((cite-link (org-element-context))
|
|
(old-type (org-element-property :type cite-link))
|
|
(begin (org-element-property :begin cite-link))
|
|
(end (org-element-property :end cite-link))
|
|
(bracketp (eq 'bracket (org-element-property :format cite-link)))
|
|
(path (org-element-property :path cite-link))
|
|
(deltap (- (point) begin)))
|
|
;; note this does not respect brackets
|
|
(setf (buffer-substring begin end)
|
|
(concat
|
|
(if bracketp "[[" "")
|
|
new-type ":" path
|
|
(if bracketp "]]" "")))
|
|
;; try to preserve the character the point is on.
|
|
(goto-char (+ begin deltap (- (length new-type) (length old-type))))))
|
|
|
|
|
|
;;* Messages for link at cursor
|
|
|
|
(defvar org-ref-message-timer nil
|
|
"Variable to store the link message timer in.")
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-show-link-messages ()
|
|
"Turn on link messages.
|
|
You will see a message in the minibuffer when on a cite, ref or
|
|
label link."
|
|
(interactive)
|
|
(or org-ref-message-timer
|
|
(setq org-ref-message-timer
|
|
(run-with-idle-timer 0.5 t 'org-ref-link-message)
|
|
org-ref-show-citation-on-enter t)))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-cancel-link-messages ()
|
|
"Stop showing messages in minibuffer when on a link."
|
|
(interactive)
|
|
(cancel-timer org-ref-message-timer)
|
|
(setq org-ref-message-timer nil
|
|
org-ref-show-citation-on-enter nil))
|
|
|
|
|
|
(when org-ref-show-citation-on-enter
|
|
(org-ref-show-link-messages))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-change-completion ()
|
|
"Change the completion backend.
|
|
Options are \"org-ref-helm-bibtex\", \"org-ref-helm-cite\",
|
|
\"org-ref-ivy-cite\" and \"org-ref-reftex\"."
|
|
(interactive)
|
|
(require
|
|
(intern
|
|
(completing-read "Backend: " '("org-ref-helm-bibtex"
|
|
"org-ref-helm-cite"
|
|
"org-ref-ivy-cite"
|
|
"org-ref-reftex")
|
|
nil
|
|
t
|
|
"org-ref-helm-cite"))))
|
|
|
|
;;** Messages for context under mouse pointer
|
|
|
|
(defvar org-ref-last-mouse-pos nil
|
|
"Stores last mouse position for use in `org-ref-mouse-message'.")
|
|
|
|
|
|
(defun org-ref-can-move-p ()
|
|
"See if a character is under the mouse.
|
|
If so return the position for `goto-char'."
|
|
(let* ((line (cddr org-ref-last-mouse-pos))
|
|
(col (cadr org-ref-last-mouse-pos)))
|
|
(save-excursion
|
|
(goto-char (window-start))
|
|
(forward-line line)
|
|
(if
|
|
(> (- (line-end-position) (line-beginning-position)) col)
|
|
(progn (forward-char col) (point))
|
|
nil))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-mouse-message ()
|
|
"Display message for link under mouse cursor."
|
|
(interactive)
|
|
(when (not (equal (mouse-position) org-ref-last-mouse-pos))
|
|
(setq org-ref-last-mouse-pos (mouse-position))
|
|
(let ((p (org-ref-can-move-p)))
|
|
(when p
|
|
(save-excursion
|
|
(goto-char p)
|
|
(org-ref-link-message))))))
|
|
|
|
|
|
(defvar org-ref-message-timer-mouse nil
|
|
"Store mouse timer.")
|
|
|
|
|
|
(defvar org-ref-mouse-message-interval 0.5
|
|
"How often to run the mouse message timer in seconds.")
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-mouse-messages-on ()
|
|
"Turn on mouse messages."
|
|
(interactive)
|
|
(or org-ref-message-timer-mouse
|
|
(setq org-ref-message-timer-mouse
|
|
(run-at-time "0.5 sec"
|
|
org-ref-mouse-message-interval
|
|
'org-ref-mouse-message))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-mouse-messages-off ()
|
|
"Turn off mouse messages."
|
|
(interactive)
|
|
(cancel-timer org-ref-message-timer-mouse)
|
|
(setq org-ref-message-timer-mouse nil)
|
|
(message "Mouse messages are off"))
|
|
|
|
|
|
|
|
;;* font lock for org-ref
|
|
|
|
(defcustom org-ref-colorize-links
|
|
t
|
|
"When non-nil, change colors of links."
|
|
:type 'boolean
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-cite-color
|
|
"forest green"
|
|
"Color of cite like links."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-ref-color
|
|
"dark red"
|
|
"Color of ref like links."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-label-color
|
|
"dark magenta"
|
|
"Color of label links."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defvar org-ref-cite-re
|
|
(concat "\\(" (mapconcat
|
|
(lambda (x)
|
|
(replace-regexp-in-string "\\*" "\\\\*" x))
|
|
org-ref-cite-types "\\|") "\\):"
|
|
"\\([a-zA-Z0-9_:\\./-]+,?\\)+")
|
|
"Regexp for cite links.
|
|
Group 1 contains the cite type.
|
|
Group 2 contains the keys.")
|
|
|
|
|
|
(defvar org-ref-label-re
|
|
"label:\\([a-zA-Z0-9_:-]+,?\\)+"
|
|
"Regexp for label links.")
|
|
|
|
|
|
(defvar org-ref-ref-re
|
|
"\\(eq\\)?ref:\\([a-zA-Z0-9_:-]+,?\\)+"
|
|
"Regexp for ref links.")
|
|
|
|
|
|
(defface org-ref-cite-face
|
|
`((t (:inherit org-link
|
|
:foreground ,org-ref-cite-color)))
|
|
"Color for cite-like links in org-ref.")
|
|
|
|
|
|
(defface org-ref-label-face
|
|
`((t (:inherit org-link :foreground ,org-ref-label-color)))
|
|
"Color for ref links in org-ref.")
|
|
|
|
|
|
(defface org-ref-ref-face
|
|
`((t (:inherit org-link :foreground ,org-ref-ref-color)))
|
|
"Face for ref links in org-ref.")
|
|
|
|
|
|
;;** Font-lock org-ref links
|
|
|
|
;; We use functions to search for the next link, and then use org-mode to find
|
|
;; the boundaries. I wasn't able to figure out robust regexps for these links
|
|
;; that includes all possible link syntaxes eg. bare, [[cite:key]] and
|
|
;; [[cite:key] [text]]. Using regexps might be a bit more efficient, so if they
|
|
;; ever get figured out, we could eliminate the org-element code in these
|
|
;; functions.
|
|
|
|
;; These functions are not used with org-9. I define them here to make
|
|
;; byte-compiling quiet.
|
|
(defun org-ref-match-next-cite-link (_) nil)
|
|
(defun org-ref-match-next-label-link (_) nil)
|
|
(defun org-ref-match-next-ref-link (_) nil)
|
|
(defun org-ref-make-org-link-cite-key-visible (_) nil)
|
|
|
|
(when (not (fboundp 'org-link-set-parameters))
|
|
|
|
(defun org-ref-match-next-cite-link (&optional limit)
|
|
"Search forward to next cite link up to LIMIT
|
|
Add a tooltip to the match."
|
|
(when (and (re-search-forward org-ref-cite-re limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
;; we think we are on a cite link lets get on it and make sure
|
|
(forward-char -2)
|
|
(let ((this-link (org-element-context)))
|
|
(if (-contains? org-ref-cite-types (org-element-property :type this-link))
|
|
;; we are on a cite link
|
|
(progn
|
|
(when org-ref-show-citation-on-enter
|
|
(add-text-properties
|
|
(org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))
|
|
(list
|
|
'help-echo (lambda (window object position)
|
|
(save-excursion
|
|
(goto-char position)
|
|
;; Here we wrap the citation string to a reasonable size.
|
|
(let ((s (org-ref-format-entry
|
|
(org-ref-get-bibtex-key-under-cursor))))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string))))))))
|
|
(set-match-data
|
|
(list (org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))))
|
|
(goto-char (org-element-property :end this-link)))
|
|
;; Must be a false match.
|
|
;; somehow were not on a
|
|
;; cite link, so we try
|
|
;; again.
|
|
(org-ref-match-next-cite-link limit)))))
|
|
|
|
|
|
(defun org-ref-match-next-label-link (limit)
|
|
"Find next label link up to LIMIT.
|
|
Add tooltip."
|
|
(if (and (re-search-forward "label:\\([[:alnum:]]\\)\\{2,\\}" limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
(progn
|
|
(forward-char -2)
|
|
(let ((this-link (org-element-context)))
|
|
(if (string= "label" (org-element-property :type this-link))
|
|
;; on a label
|
|
(progn
|
|
(add-text-properties
|
|
(org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))
|
|
(list
|
|
'help-echo (lambda (window object position)
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let ((s (org-ref-link-message)))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string)))))))
|
|
(set-match-data
|
|
(list (org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))))
|
|
(goto-char (org-element-property :end this-link)))
|
|
;; false match
|
|
(org-ref-match-next-label-link limit))))))
|
|
|
|
|
|
(defun org-ref-match-next-ref-link (limit)
|
|
"Find next ref link up to LIMIT.
|
|
Add tooltip to the link. We avoid tags by not finding :ref: in
|
|
tags."
|
|
(when (and (re-search-forward "[^:]\\(eq\\)?ref:\\([[:alnum:]]\\)\\{2,\\}" limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
;; we think we are on a ref link, lets make sure.
|
|
(forward-char -2)
|
|
(let ((this-link (org-element-context)))
|
|
(if (-contains? org-ref-ref-types
|
|
(org-element-property :type this-link))
|
|
;; we are, so we do our business
|
|
(progn
|
|
(add-text-properties
|
|
(org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))
|
|
(list
|
|
'help-echo (lambda (window object position)
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let ((s (org-ref-link-message)))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string)))))))
|
|
(set-match-data
|
|
(list (org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))))
|
|
(goto-char (org-element-property :end this-link)))
|
|
;; False match, let's try again
|
|
(org-ref-match-next-ref-link limit)))))
|
|
|
|
|
|
(defun org-ref-match-next-bibliography-link (limit)
|
|
"Find next bibliography link up to LIMIT.
|
|
Add tooltip to the link."
|
|
(when (and (re-search-forward "bibliography:\\([[:alnum:]]\\)\\{2,\\}" limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
(forward-char -2)
|
|
(let ((this-link (org-element-context)))
|
|
(add-text-properties
|
|
(org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))
|
|
(list
|
|
'help-echo (lambda (window object position)
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let ((s (org-ref-link-message)))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string)))))))
|
|
(set-match-data
|
|
(list (org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))))
|
|
(goto-char (org-element-property :end this-link)))))
|
|
|
|
|
|
(defun org-ref-match-next-bibliographystyle-link (limit)
|
|
"Find next bibliographystyle link up to LIMIT.
|
|
Add tooltip to the link."
|
|
(when (and (re-search-forward "bibliographystyle:\\([[:alnum:]]\\)\\{2,\\}" limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
(forward-char -2)
|
|
(let* ((this-link (org-element-context))
|
|
(path (org-element-property :path this-link))
|
|
(msg (shell-command-to-string (format "kpsewhich %s.bst" path))))
|
|
(add-text-properties
|
|
(org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))
|
|
(list
|
|
'help-echo msg))
|
|
(set-match-data
|
|
(list (org-element-property :begin this-link)
|
|
(- (org-element-property :end this-link)
|
|
(org-element-property :post-blank this-link))))
|
|
(goto-char (org-element-property :end this-link)))))
|
|
|
|
|
|
(defun org-ref-make-org-link-cite-key-visible (&rest _)
|
|
"Make the org-ref cite link visible in descriptive links."
|
|
(when (string-match-p "\\.org$\\|\\.txt$" (buffer-name))
|
|
(save-match-data
|
|
(let ((s (match-string 1))
|
|
(s-begin (match-beginning 1))
|
|
(s-end (match-end 1))
|
|
(beg (match-beginning 0))
|
|
(end (match-end 0))
|
|
(cite-re (format "^\\(%s:\\)"
|
|
(regexp-opt (-sort
|
|
(lambda (a b)
|
|
(> (length a) (length b)))
|
|
org-ref-cite-types))))
|
|
cite-type)
|
|
|
|
(when (and s (string-match cite-re s))
|
|
(setq cite-type (match-string 1 s))
|
|
(remove-text-properties beg end
|
|
'(invisible))
|
|
(add-text-properties
|
|
beg end
|
|
`(face (:foreground ,org-ref-cite-color))))))))
|
|
|
|
(when org-ref-colorize-links
|
|
(add-hook
|
|
'org-mode-hook
|
|
(lambda ()
|
|
(advice-add 'org-activate-bracket-links :after #'org-ref-make-org-link-cite-key-visible)
|
|
(font-lock-add-keywords
|
|
nil
|
|
'((org-ref-match-next-cite-link (0 'org-ref-cite-face t))
|
|
(org-ref-match-next-label-link (0 'org-ref-label-face t))
|
|
(org-ref-match-next-ref-link (0 'org-ref-ref-face t))
|
|
(org-ref-match-next-bibliography-link (0 'org-link t))
|
|
(org-ref-match-next-bibliographystyle-link (0 'org-link t)))
|
|
t)))))
|
|
|
|
|
|
;;* Links
|
|
;;** bibliography and bibliographystyle
|
|
(defun org-ref-open-bibliography-no-org (link-string)
|
|
"Open a bibliography link when you are not in org-mode.
|
|
This means you cannot use the usual org-machinery to figure it
|
|
out. We don't try to be clever here. If there is only one file,
|
|
we open it, otherwise prompt for which one to open."
|
|
(let ((bibfiles (split-string link-string ",")))
|
|
(find-file (if (= 1 (length bibfiles))
|
|
(car bibfiles)
|
|
(completing-read
|
|
"Bib file: " bibfiles nil t)))))
|
|
|
|
|
|
(defun org-ref-bibinputs ()
|
|
"Feed BIBINPUTS environment variable to `parse-colon-path'."
|
|
(parse-colon-path (getenv "BIBINPUTS")))
|
|
|
|
|
|
(defun org-ref-bibfile-kpsewhich (bibfile)
|
|
"Try to find BIBFILE using kpsewhich."
|
|
(let ((f (replace-regexp-in-string
|
|
"\n$" ""
|
|
(shell-command-to-string (format "kpsewhich %s" bibfile)))))
|
|
(unless (string= "" f)
|
|
f)))
|
|
|
|
|
|
(defun org-ref-find-bibfile (bibfile)
|
|
"Find BIBFILE as local file, or using kpsewhich or bibinputs."
|
|
(or (if (file-exists-p bibfile) bibfile)
|
|
(org-ref-bibfile-kpsewhich bibfile)
|
|
;; this should never be reached if bibfile exists, because kpsewhich is
|
|
;; stronger
|
|
(org-ref-locate-file bibfile (org-ref-bibinputs))))
|
|
|
|
|
|
(defun org-ref-locate-file (filename path)
|
|
"Search for FILENAME through PATH.
|
|
Like `locate-file-internal', but with `file-exists-p' as
|
|
PREDICATE."
|
|
(locate-file-internal filename path () #'file-exists-p))
|
|
|
|
|
|
(defun org-ref-open-bibliography (link-string)
|
|
"The click function for a bibliography link."
|
|
;; get link-string boundaries we have to go to the
|
|
;; beginning of the line, and then search forward
|
|
(if (not (eq major-mode 'org-mode))
|
|
(org-ref-open-bibliography-no-org link-string)
|
|
(let* ((bibfile)
|
|
;; object is the link you clicked on
|
|
(object (org-element-context))
|
|
(link-string-beginning)
|
|
(link-string-end)
|
|
(cp (point)))
|
|
(save-excursion
|
|
(goto-char (org-element-property :begin object))
|
|
(search-forward link-string nil nil 1)
|
|
(setq link-string-beginning (match-beginning 0))
|
|
(setq link-string-end (match-end 0)))
|
|
|
|
;; Make sure point is in the link-path.
|
|
(if (< cp link-string-beginning)
|
|
(goto-char link-string-beginning))
|
|
;; We set the reftex-default-bibliography
|
|
;; here. it should be a local variable only in
|
|
;; the current buffer. We need this for using
|
|
;; reftex to do citations.
|
|
(set (make-local-variable 'reftex-default-bibliography)
|
|
(split-string
|
|
(org-element-property :path object) ","))
|
|
|
|
(let (key-beginning key-end)
|
|
;; now if we have comma separated bibliographies
|
|
;; we find the one clicked on. we want to
|
|
;; search forward to next comma from point
|
|
(save-excursion
|
|
(if (search-forward "," link-string-end 1 1)
|
|
;; we found a match
|
|
(setq key-end (- (match-end 0) 1))
|
|
;; no comma found so take the point
|
|
(setq key-end (point))))
|
|
;; and backward to previous comma from point
|
|
(save-excursion
|
|
(if (search-backward "," link-string-beginning 1 1)
|
|
;; we found a match
|
|
(setq key-beginning (+ (match-beginning 0) 1))
|
|
(setq key-beginning (point)))) ; no match found
|
|
;; save the key we clicked on.
|
|
(setq bibfile (org-ref-strip-string
|
|
(buffer-substring key-beginning key-end)))
|
|
;; open file on click. I use or because org-ref-find-bibfile returns nil
|
|
;; if the file doesn't exist, and clicking should open the file in that
|
|
;; case.
|
|
(find-file (or (org-ref-find-bibfile bibfile) bibfile))))))
|
|
|
|
|
|
(defun org-ref-bibliography-format (keyword desc format)
|
|
"Formatting function for bibliography links."
|
|
(cond
|
|
((eq format 'org) (org-ref-get-org-bibliography))
|
|
((eq format 'ascii) (org-ref-get-ascii-bibliography))
|
|
((eq format 'md) (org-ref-get-md-bibliography))
|
|
((eq format 'odt) (org-ref-get-odt-bibliography))
|
|
((eq format 'html) (org-ref-get-html-bibliography))
|
|
((eq format 'latex)
|
|
;; write out the latex bibliography command
|
|
(format "\\bibliography{%s}"
|
|
(replace-regexp-in-string
|
|
"\\.bib" ""
|
|
(mapconcat
|
|
'identity
|
|
(mapcar 'file-relative-name
|
|
(split-string keyword ","))
|
|
","))))))
|
|
|
|
|
|
(defun org-bibliography-complete-link (&optional arg)
|
|
"Completion function for bibliography link.
|
|
This will scan the org-file for citations, and if it finds the
|
|
citation keys in `org-ref-default-bibliography' or bib files in
|
|
the current directory it will insert them. Otherwise you will be
|
|
prompted for a file.
|
|
|
|
ARG does nothing. I think it is a required signature."
|
|
(let* ((keys (reverse (org-ref-get-bibtex-keys)))
|
|
(possible-files (org-ref-possible-bibfiles))
|
|
(found '()))
|
|
|
|
;; remove bib links, we will be replacing them. It is debatable if this is a
|
|
;; good idea. I could easily be persuaded not to do this, but it is also not
|
|
;; a great idea to have multiple bibliography links.
|
|
(org-element-map (org-ref-parse-buffer)
|
|
'link (lambda (link)
|
|
(when (string= "bibliography"
|
|
(org-element-property :type link))
|
|
(setf (buffer-substring (org-element-property :begin link)
|
|
(org-element-property :end link))
|
|
""))))
|
|
|
|
;; Get bibfiles containing keys
|
|
(setq found
|
|
(mapconcat #'identity
|
|
(-uniq (cl-loop for key in keys
|
|
collect
|
|
(catch 'result
|
|
(cl-loop for file in possible-files do
|
|
(if (org-ref-key-in-file-p
|
|
key
|
|
(file-truename file))
|
|
(throw 'result
|
|
(file-relative-name file)))))))
|
|
","))
|
|
(concat "bibliography:"
|
|
(if (string= found "")
|
|
(completing-read "Bibfile: " possible-files)
|
|
found))))
|
|
|
|
|
|
(defun org-ref-bibliography-face-fn (path)
|
|
"Return face for a bibliography link.
|
|
org-link if the files exist.
|
|
font-lock-warning-face if any file does not exist."
|
|
(save-match-data
|
|
(cond
|
|
((or (not org-ref-show-broken-links)
|
|
(-every?
|
|
'identity
|
|
(mapcar
|
|
(lambda (bibfile)
|
|
(file-exists-p bibfile))
|
|
(split-string path ","))))
|
|
'org-link)
|
|
(t
|
|
'font-lock-warning-face))))
|
|
|
|
|
|
(org-ref-link-set-parameters "bibliography"
|
|
:follow #'org-ref-open-bibliography
|
|
:export #'org-ref-bibliography-format
|
|
:complete #'org-bibliography-complete-link
|
|
:help-echo (lambda (window object position)
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let ((s (org-ref-link-message)))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string)))))
|
|
:face #'org-ref-bibliography-face-fn)
|
|
|
|
|
|
(defun org-ref-nobibliography-format (keyword desc format)
|
|
"Format function for nobibliography link export"
|
|
(cond
|
|
((eq format 'org) (org-ref-get-org-bibliography))
|
|
((eq format 'ascii) (org-ref-get-ascii-bibliography))
|
|
((eq format 'odt) (org-ref-get-ascii-bibliography))
|
|
((eq format 'html) (org-ref-get-html-bibliography))
|
|
((eq format 'latex)
|
|
;; write out the latex bibliography command
|
|
(format "\\nobibliography{%s}"
|
|
(replace-regexp-in-string
|
|
"\\.bib" ""
|
|
(mapconcat 'identity
|
|
(mapcar 'file-relative-name
|
|
(split-string keyword ","))
|
|
","))))))
|
|
|
|
|
|
(org-ref-link-set-parameters "nobibliography"
|
|
:follow #'org-ref-open-bibliography
|
|
:export #'org-ref-nobibliography-format)
|
|
|
|
|
|
(org-ref-link-set-parameters "printbibliography"
|
|
:follow #'org-ref-open-bibliography
|
|
:export (lambda (keyword desc format)
|
|
(cond
|
|
((eq format 'org) (org-ref-get-org-bibliography))
|
|
((eq format 'html) (org-ref-get-html-bibliography))
|
|
((eq format 'latex)
|
|
;; write out the biblatex bibliography command
|
|
org-ref-printbibliography-cmd))))
|
|
|
|
|
|
(org-ref-link-set-parameters "bibliographystyle"
|
|
:export (lambda (keyword desc format)
|
|
(cond
|
|
((or (eq format 'latex)
|
|
(eq format 'beamer))
|
|
;; write out the latex bibliography command
|
|
(format "\\bibliographystyle{%s}" keyword))
|
|
;; Other styles should not have an output for this
|
|
(t
|
|
""))))
|
|
|
|
(defun org-bibliographystyle-complete-link (&optional arg)
|
|
"Completion function for bibliography style links."
|
|
(when (executable-find "kpsewhich")
|
|
(concat "bibliographystyle:"
|
|
(completing-read "Style: " (mapcar 'file-name-nondirectory
|
|
(mapcar 'file-name-sans-extension
|
|
(-flatten
|
|
(mapcar (lambda (path)
|
|
(setq path (replace-regexp-in-string "!" "" path))
|
|
(when (file-directory-p path)
|
|
(f-entries path (lambda (f) (f-ext? f "bst")) t)))
|
|
(split-string
|
|
;; https://tex.stackexchange.com/questions/431948/get-a-list-of-installed-bibliography-styles-with-kpsewhich?noredirect=1#comment1082436_431948
|
|
(shell-command-to-string "kpsewhich -expand-path '$BSTINPUTS'")
|
|
":")))))))))
|
|
|
|
|
|
(defun org-ref-insert-bibliographystyle-link ()
|
|
"Insert a bibliographystyle link with completion."
|
|
(interactive)
|
|
(insert (org-bibliographystyle-complete-link)))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-insert-bibliography-link ()
|
|
"Insert a bibliography with completion."
|
|
(interactive)
|
|
(insert (org-bibliography-complete-link)))
|
|
|
|
|
|
;;** addbibresource
|
|
|
|
(defun org-ref-follow-addbibresource (link-string)
|
|
;; get link-string boundaries. we have to go to the
|
|
;; beginning of the line, and then search forward
|
|
(let* ((bibfile)
|
|
;; object is the link you clicked on
|
|
(object (org-element-context))
|
|
|
|
(link-string-beginning)
|
|
(link-string-end))
|
|
|
|
(save-excursion
|
|
(goto-char (org-element-property :begin object))
|
|
(search-forward link-string nil nil 1)
|
|
(setq link-string-beginning (match-beginning 0))
|
|
(setq link-string-end (match-end 0)))
|
|
|
|
;; We set the reftex-default-bibliography
|
|
;; here. it should be a local variable only in
|
|
;; the current buffer. We need this for using
|
|
;; reftex to do citations.
|
|
(set (make-local-variable 'reftex-default-bibliography)
|
|
(split-string (org-element-property :path object) ","))
|
|
|
|
(let (key-beginning key-end)
|
|
;; now if we have comma separated bibliographies
|
|
;; we find the one clicked on. we want to
|
|
;; search forward to next comma from point
|
|
(save-excursion
|
|
(if (search-forward "," link-string-end 1 1)
|
|
(setq key-end (- (match-end 0) 1)) ; we found a match
|
|
(setq key-end (point)))) ; no comma found so take the point
|
|
;; and backward to previous comma from point
|
|
(save-excursion
|
|
(if (search-backward "," link-string-beginning 1 1)
|
|
(setq key-beginning (+ (match-beginning 0) 1)) ; we found a match
|
|
(setq key-beginning (point)))) ; no match found
|
|
;; save the key we clicked on.
|
|
(setq bibfile (org-ref-strip-string
|
|
(buffer-substring key-beginning key-end)))
|
|
(find-file bibfile))))
|
|
|
|
|
|
(org-ref-link-set-parameters "addbibresource"
|
|
:follow #'org-ref-follow-addbibresource
|
|
:export (lambda (keyword desc format)
|
|
(cond
|
|
((eq format 'html) (format "")) ; no output for html
|
|
((eq format 'latex)
|
|
;; write out the latex addbibresource command
|
|
(format "\\addbibresource{%s}" keyword)))))
|
|
|
|
;;** List of figures
|
|
|
|
;; org-in-commented-heading-p was introduced in org commit 6d1d61f6. Org version
|
|
;; 8.3 was the first version to contain this function. This is provided for
|
|
;; backward compatibility for the org-mode included with Emacs.
|
|
(unless (fboundp 'org-in-commented-heading-p)
|
|
(defun org-in-commented-heading-p (&optional no-inheritance)
|
|
"Non-nil if point is under a commented heading.
|
|
This function also checks ancestors of the current headline,
|
|
unless optional argument NO-INHERITANCE is non-nil."
|
|
(cond
|
|
((org-before-first-heading-p) nil)
|
|
((let ((headline (nth 4 (org-heading-components))))
|
|
(and headline
|
|
(let ((case-fold-search nil))
|
|
(org-string-match-p
|
|
(concat
|
|
"^"
|
|
org-comment-string "\\(?: \\|$\\)")
|
|
headline)))))
|
|
(no-inheritance nil)
|
|
(t
|
|
(save-excursion (and (org-up-heading-safe) (org-in-commented-heading-p)))))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-list-of-figures (&optional arg)
|
|
"Generate buffer with list of figures in them.
|
|
ARG does nothing.
|
|
Ignore figures in COMMENTED sections."
|
|
(interactive)
|
|
(save-excursion
|
|
(widen)
|
|
(let* ((c-b (buffer-name))
|
|
(counter 0)
|
|
(list-of-figures
|
|
(org-element-map (org-ref-parse-buffer) 'link
|
|
(lambda (link)
|
|
"create a link for to the figure"
|
|
(when
|
|
(and (string= (org-element-property :type link) "file")
|
|
(string-match-p
|
|
"[^.]*\\.\\(png\\|jpg\\|eps\\|pdf\\|svg\\)$"
|
|
(org-element-property :path link))
|
|
;; ignore commented sections
|
|
(save-excursion
|
|
(goto-char (org-element-property :begin link))
|
|
(not (or (org-in-commented-heading-p)
|
|
(org-at-comment-p)
|
|
(-intersection (org-get-tags) org-export-exclude-tags)))))
|
|
(cl-incf counter)
|
|
|
|
(let* ((start (org-element-property :begin link))
|
|
(linenum (progn (goto-char start) (line-number-at-pos)))
|
|
(fname (org-element-property :path link))
|
|
(parent (car (cdr
|
|
(org-element-property :parent link))))
|
|
(caption (cl-caaar (plist-get parent :caption)))
|
|
(name (plist-get parent :name)))
|
|
|
|
(if caption
|
|
(format "[[file:%s::%s][Figure %s:]] %s\n" c-b linenum counter caption)
|
|
;; if it has no caption, try the name
|
|
;; if it has no name, use the file name
|
|
(cond (name
|
|
(format "[[file:%s::%s][Figure %s:]] %s\n" c-b linenum counter name))
|
|
(fname
|
|
(format "[[file:%s::%s][Figure %s:]] %s\n"
|
|
c-b linenum counter fname))))))))))
|
|
(switch-to-buffer "*List of Figures*")
|
|
(setq buffer-read-only nil)
|
|
(org-mode)
|
|
(erase-buffer)
|
|
(insert (mapconcat 'identity list-of-figures ""))
|
|
(goto-char (point-min))
|
|
;; open links in the same window
|
|
(setq-local org-link-frame-setup
|
|
'((file . find-file)))
|
|
(setq buffer-read-only t)
|
|
(use-local-map (copy-keymap org-mode-map))
|
|
(local-set-key "q" #'(lambda () (interactive) (kill-buffer))))))
|
|
|
|
|
|
(org-ref-link-set-parameters "list-of-figures"
|
|
:follow #'org-ref-list-of-figures
|
|
:export (lambda (keyword desc format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\listoffigures")))))
|
|
|
|
;;** List of tables
|
|
;;;###autoload
|
|
(defun org-ref-list-of-tables (&optional arg)
|
|
"Generate a buffer with a list of tables.
|
|
ARG does nothing."
|
|
(interactive)
|
|
(save-excursion
|
|
(widen)
|
|
(let* ((c-b (buffer-name))
|
|
(counter 0)
|
|
(list-of-tables
|
|
(org-element-map (org-ref-parse-buffer 'element) 'table
|
|
(lambda (table)
|
|
"create a link for to the table"
|
|
(save-excursion
|
|
(when
|
|
;; ignore commented sections
|
|
(goto-char (org-element-property :begin table))
|
|
(not (or (org-in-commented-heading-p)
|
|
(-intersection (org-get-tags) org-export-exclude-tags)))
|
|
(cl-incf counter)
|
|
(let* ((start (org-element-property :begin table))
|
|
(linenum (progn (goto-char start) (line-number-at-pos)))
|
|
(caption (cl-caaar (org-element-property :caption table)))
|
|
(name (org-element-property :name table)))
|
|
(if caption
|
|
(format "[[file:%s::%s][Table %s:]] %s\n" c-b linenum counter caption)
|
|
;; if it has no caption, try the name
|
|
;; if it has no name, use generic name
|
|
(cond (name
|
|
(format "[[file:%s::%s][Table %s:]] %s\n"
|
|
c-b linenum counter name))
|
|
(t
|
|
(format "[[file:%s::%s][Table %s:]] No caption\n"
|
|
c-b linenum counter)))))))))))
|
|
(switch-to-buffer "*List of Tables*")
|
|
(setq buffer-read-only nil)
|
|
(org-mode)
|
|
(erase-buffer)
|
|
(insert (mapconcat 'identity list-of-tables ""))
|
|
(goto-char (point-min))
|
|
;; open links in the same window
|
|
(setq-local org-link-frame-setup
|
|
'((file . find-file)))
|
|
(setq buffer-read-only t)
|
|
(use-local-map (copy-keymap org-mode-map))
|
|
(local-set-key "q" #'(lambda () (interactive) (kill-buffer))))))
|
|
|
|
|
|
(org-ref-link-set-parameters "list-of-tables"
|
|
:follow #'org-ref-list-of-tables
|
|
:export (lambda (keyword desc format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\listoftables")))))
|
|
|
|
;;** label link
|
|
|
|
(defun org-ref-count-labels (label)
|
|
"Count number of LABELs in the document."
|
|
(+ (count-matches
|
|
(format "label:%s\\( \\|]\\|$\\)" (regexp-quote label))
|
|
(point-min) (point-max))
|
|
(count-matches
|
|
(format "<<%s>>" label)
|
|
(point-min) (point-max))
|
|
;; for tblname, it is not enough to get word boundary
|
|
;; tab-little and tab-little-2 match then.
|
|
(count-matches
|
|
(format "^\\( \\)*#\\+tblname:\\s-*%s\\b[^-:]" label)
|
|
(point-min) (point-max))
|
|
(count-matches (format "\\label{%s}" label)
|
|
(point-min) (point-max))
|
|
;; this is the org-format #+label:
|
|
(count-matches (format "^\\( \\)*#\\+label:\\s-*%s\\b[^-:]" label)
|
|
(point-min) (point-max))
|
|
;; #+name:
|
|
(count-matches (format "^\\( \\)*#\\+name:\\s-*%s\\b[^-:]" label)
|
|
(point-min) (point-max))
|
|
(let ((custom-id-count 0))
|
|
(org-map-entries
|
|
(lambda ()
|
|
(when (string= label (org-entry-get (point) "CUSTOM_ID"))
|
|
(setq custom-id-count (+ 1 custom-id-count)))))
|
|
custom-id-count)))
|
|
|
|
|
|
(defun org-label-store-link ()
|
|
"Store a link to a label. The output will be a ref to that label."
|
|
;; First we have to make sure we are on a label link.
|
|
(let* ((object (and (eq major-mode 'org-mode) (org-element-context))))
|
|
(when (and
|
|
(equal (org-element-type object) 'link)
|
|
(equal (org-element-property :type object) "label"))
|
|
(org-store-link-props
|
|
:type "ref"
|
|
:link (concat "ref:" (org-element-property :path object))))
|
|
|
|
;; Store link on table
|
|
(when (equal (org-element-type object) 'table)
|
|
(org-store-link-props
|
|
:type "ref"
|
|
:link (concat "ref:" (org-element-property :name object))))
|
|
|
|
;; store link on heading with custom_id
|
|
;; this is not a ref link, but it is still what you want
|
|
(when (and (equal (org-element-type object) 'headline)
|
|
(org-entry-get (point) "CUSTOM_ID"))
|
|
(org-store-link-props
|
|
:type "custom_id"
|
|
:link (format "[[#%s]]" (org-entry-get (point) "CUSTOM_ID"))))
|
|
|
|
;; and to #+label: lines
|
|
(when (and (equal (org-element-type object) 'paragraph)
|
|
(org-element-property :name object))
|
|
(org-store-link-props
|
|
:type "ref"
|
|
:link (concat "ref:" (org-element-property :name object))))))
|
|
|
|
|
|
(defun org-ref-label-face-fn (label)
|
|
"Return a face for the label link."
|
|
(save-match-data
|
|
(cond
|
|
((or (not org-ref-show-broken-links)
|
|
(= 1 (org-ref-count-labels label)))
|
|
'org-ref-label-face)
|
|
(t
|
|
'font-lock-warning-face))))
|
|
|
|
|
|
(org-ref-link-set-parameters "label"
|
|
:follow (lambda (label)
|
|
"On clicking count the number of label tags used in the buffer.
|
|
A number greater than one means multiple labels!"
|
|
(let ((count (org-ref-count-labels label)))
|
|
(message (format "%s occurence%s"
|
|
count
|
|
(if (or (= count 0)
|
|
(> count 1))
|
|
"s"
|
|
""))
|
|
(org-ref-count-labels label))))
|
|
:export (lambda (keyword desc format)
|
|
(cond
|
|
((eq format 'html) (format "<div id=\"%s\"></div>" keyword))
|
|
((eq format 'md) (format "<a name=\"%s\"></a>" keyword))
|
|
((eq format 'latex)
|
|
(format "\\label{%s}" keyword))))
|
|
:store #'org-label-store-link
|
|
:face 'org-ref-label-face-fn
|
|
:help-echo (lambda (window object position)
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let ((s (org-ref-link-message)))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string))))))
|
|
|
|
;;** ref link
|
|
|
|
(defun org-ref-ref-follow (label)
|
|
"On clicking goto the LABEL.
|
|
Navigate back with \`\\[org-mark-ring-goto]'."
|
|
;; Suppress minibuffer message in helm. See `org-ref-browser'.
|
|
(if (and (boundp 'helm-alive-p) helm-alive-p)
|
|
(lambda (&optional pos buffer)
|
|
(setq pos (or pos (point)))
|
|
(setq org-mark-ring (nthcdr (1- org-mark-ring-length) org-mark-ring))
|
|
(move-marker (car org-mark-ring)
|
|
(or pos (point))
|
|
(or buffer (current-buffer))))
|
|
(org-mark-ring-push))
|
|
;; next search from beginning of the buffer it is possible you would not find
|
|
;; the label if narrowing is in effect
|
|
(widen)
|
|
(unless
|
|
(or
|
|
;; our label links
|
|
(progn
|
|
(goto-char (point-min))
|
|
(re-search-forward (format "label:%s\\b" (regexp-quote label)) nil t))
|
|
|
|
;; a latex label
|
|
(progn
|
|
(goto-char (point-min))
|
|
(re-search-forward (format "\\label{%s}" (regexp-quote label)) nil t))
|
|
|
|
;; #+label: name org-definition
|
|
(progn
|
|
(goto-char (point-min))
|
|
(re-search-forward
|
|
(format "^\\( \\)*#\\+label:\\s-*\\(%s\\)\\b" (regexp-quote label)) nil t))
|
|
|
|
;; org tblname
|
|
(progn
|
|
(goto-char (point-min))
|
|
(re-search-forward
|
|
(format "^\\( \\)*#\\+tblname:\\s-*\\(%s\\)\\b" (regexp-quote label)) nil t))
|
|
|
|
;; a #+name
|
|
(progn
|
|
(goto-char (point-min))
|
|
(re-search-forward
|
|
(format "^\\( \\)*#\\+name:\\s-*\\(%s\\)\\b" (regexp-quote label)) nil t))
|
|
|
|
;; CUSTOM_ID
|
|
(progn
|
|
(goto-char (point-min))
|
|
(let ((p (org-map-entries
|
|
(lambda ()
|
|
(point))
|
|
(format "CUSTOM_ID=\"%s\"" label))))
|
|
(if (not (= 1 (length p)))
|
|
nil
|
|
(goto-char (car p)))))
|
|
(progn
|
|
(goto-char (point-min))
|
|
(re-search-forward
|
|
(format "<<%s>>" (regexp-quote label)) nil t)))
|
|
|
|
;; we did not find anything, so go back to where we came
|
|
(org-mark-ring-goto)
|
|
(error "%s not found" label))
|
|
(org-show-entry)
|
|
(unless (and (boundp 'helm-alive-p) helm-alive-p)
|
|
(substitute-command-keys
|
|
"Go back with (org-mark-ring-goto) \`\\[org-mark-ring-goto]'.")))
|
|
|
|
|
|
(defun org-ref-complete-link (&optional arg)
|
|
"Completion function for ref links.
|
|
Optional argument ARG Does nothing."
|
|
(let ((label))
|
|
(setq label (completing-read "label: " (org-ref-get-labels)))
|
|
(format "ref:%s" label)))
|
|
|
|
|
|
(defun org-ref-ref-help-echo (window object position)
|
|
"A help-echo function for ref links."
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let ((s (org-ref-link-message)))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string)))))
|
|
|
|
|
|
(defun org-ref-ref-export (keyword desc format)
|
|
"An export function for ref links."
|
|
(cond
|
|
((eq format 'html)
|
|
(format "<a href=\"#%s\">%s</a>" keyword (or desc keyword)))
|
|
((eq format 'latex)
|
|
(format "\\ref{%s}" keyword))
|
|
((eq format 'md)
|
|
(format "[%s](#%s)" keyword keyword))))
|
|
|
|
|
|
(defun org-ref-ref-face-fn (label)
|
|
"Return a face for a ref link."
|
|
(save-match-data
|
|
(cond
|
|
((or (not org-ref-show-broken-links)
|
|
(member label (org-ref-get-labels)))
|
|
'org-ref-ref-face)
|
|
(t
|
|
'font-lock-warning-face))))
|
|
|
|
|
|
(org-ref-link-set-parameters "ref"
|
|
:follow #'org-ref-ref-follow
|
|
:export #'org-ref-ref-export
|
|
:complete #'org-ref-complete-link
|
|
:face 'org-ref-ref-face-fn
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
|
|
(defun org-ref-get-org-labels ()
|
|
"Return a list of #+LABEL: labels."
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(let ((matches '()))
|
|
(while (re-search-forward "^#\\+label:\\s-+\\(.*\\)\\b" (point-max) t)
|
|
;; do not do this for tables. We get those in `org-ref-get-tblnames'.
|
|
;; who would have thought you have save match data here? Trust me. When
|
|
;; I wrote this, you did.
|
|
(unless (save-match-data (equal (car (org-element-at-point)) 'table))
|
|
(add-to-list 'matches (match-string-no-properties 1) t)))
|
|
matches)))
|
|
|
|
|
|
(defun org-ref-get-custom-ids ()
|
|
"Return a list of custom_id properties in the buffer."
|
|
(let ((results '()) custom_id)
|
|
(org-map-entries
|
|
(lambda ()
|
|
(let ((custom_id (org-entry-get (point) "CUSTOM_ID")))
|
|
(when (not (null custom_id))
|
|
(setq results (append results (list custom_id)))))))
|
|
results))
|
|
|
|
|
|
(defun org-ref-get-latex-labels ()
|
|
"Return list of matchin LaTeX defined labels in buffer."
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(let ((matches '()))
|
|
(while (re-search-forward "\\\\label{\\([-a-zA-Z0-9:_\\.]*\\)}"
|
|
(point-max) t)
|
|
(add-to-list 'matches (match-string-no-properties 1) t))
|
|
matches)))
|
|
|
|
|
|
(defun org-ref-get-tblnames ()
|
|
"Return list of table names in the buffer."
|
|
(org-element-map (org-ref-parse-buffer) 'table
|
|
(lambda (table)
|
|
(org-element-property :name table))))
|
|
|
|
|
|
(defun org-ref-get-names ()
|
|
"Return list of names in the buffer."
|
|
(save-excursion
|
|
(save-restriction
|
|
(widen)
|
|
(goto-char (point-min))
|
|
(let ((case-fold-search t)
|
|
(matches '()))
|
|
(while (re-search-forward "^\\( \\)*#\\+name:\\s-+\\(.*\\)" nil t)
|
|
(cl-pushnew (match-string 2) matches))
|
|
matches))))
|
|
|
|
|
|
(defvar-local org-ref-labels '()
|
|
"Known labels in org-ref
|
|
Stores a list of strings.")
|
|
|
|
|
|
(defvar org-ref-label-regexps
|
|
'(;; #+label:
|
|
"^#\\+label:\\s-+\\(?1:[+a-zA-Z0-9:\\._-]*\\)\\_>"
|
|
;; CUSTOM_ID in a heading
|
|
":CUSTOM_ID:\\s-+\\(?1:[+a-zA-Z0-9:\\._-]*\\)\\_>"
|
|
;; #+name
|
|
"^\\s-*#\\+name:\\s-+\\(?1:[+a-zA-Z0-9:\\._-]*\\)\\_>"
|
|
;; radio targets
|
|
"<<\\(?1:[+a-zA-Z0-9:\\._-]*\\)>>"
|
|
;; #+tblname:
|
|
"^\\s-*#\\+tblname:\\s-+\\(?1:[+a-zA-Z0-9:\\._-]*\\)\\_>"
|
|
;; label links
|
|
"label:\\(?1:[+a-zA-Z0-9:\\._-]*\\)"
|
|
;; labels in latex
|
|
"\\\\label{\\(?1:[+a-zA-Z0-9:\\._-]*\\)}")
|
|
"List of regexps that are labels in org-ref.")
|
|
|
|
|
|
(defvar org-ref-label-debug nil "If non-nil print debug messages.")
|
|
|
|
|
|
(defvar-local org-ref-last-label-end 0
|
|
"Last end of position added.")
|
|
|
|
|
|
(defun org-ref-add-labels (start end)
|
|
"Add labels in the region from START to END.
|
|
This is run by font-lock. START tends to be the beginning of the
|
|
line, and END tends to be where the point is, so this function
|
|
seems to work fine at recognizing labels by the regexps in
|
|
`org-ref-label-regexps'."
|
|
(interactive "r")
|
|
(save-excursion
|
|
(save-match-data
|
|
(cl-loop for rx in org-ref-label-regexps
|
|
do
|
|
(goto-char start)
|
|
(while (re-search-forward rx end t)
|
|
(let ((label (match-string-no-properties 1)))
|
|
;; I don't know why this gets found, but some labels are
|
|
;; empty strings. we don't store these.
|
|
(unless (string= "" label)
|
|
;; if the last end is the new end -1 we are adding to a
|
|
;; label, and should pop the old one off before adding the
|
|
;; new one.
|
|
(when (eq org-ref-last-label-end (- end 1))
|
|
(pop org-ref-labels))
|
|
(with-silent-modifications
|
|
(put-text-property (match-beginning 1)
|
|
(match-end 1)
|
|
'org-ref-label t)
|
|
(put-text-property (match-beginning 1)
|
|
(match-end 1)
|
|
'rear-nonsticky '(org-ref-label)))
|
|
(when org-ref-label-debug
|
|
(message "oral: adding %s" label))
|
|
|
|
(pushnew label
|
|
org-ref-labels :test 'string=)
|
|
;; now store the last end so we can tell for the next run
|
|
;; if we are adding to a label.
|
|
(setq org-ref-last-label-end end))))))))
|
|
|
|
|
|
(defun org-ref-delete-labels (start end)
|
|
"Check for labels between START and END and remove them from the known list.
|
|
This is called as a `before-change-functions' and it means text
|
|
from START to END has been deleted.
|
|
|
|
This idea was suggested by @vspinu.
|
|
|
|
This is harder than it seems. The main issue is that START and
|
|
END here may be the same if the change is an insertion, different
|
|
by one for a simple character delete, or bigger if a selection is
|
|
deleted.
|
|
|
|
Note: this will not necessarily trigger fontification on ref
|
|
links so they might not look broken right away if their label is
|
|
missing."
|
|
;; This conditional is here because I get errors like Args out of range: 176,
|
|
;; 176 which seem to be triggered by get-text-property. I also find that this
|
|
;; can happen during export. The check on `org-export-current-backend' is not
|
|
;; perfect, this can be nil for anonyomous derived backends.
|
|
(when (and org-ref-labels
|
|
(not org-export-current-backend))
|
|
(if (not (eq start end))
|
|
;; we are in a deletion. The text from start to end will be deleted
|
|
(progn
|
|
;; start is on a label, so remove back.
|
|
(when (get-text-property start 'org-ref-label)
|
|
(let ((label (buffer-substring-no-properties
|
|
(previous-single-property-change start 'org-ref-label)
|
|
(next-single-property-change start 'org-ref-label))))
|
|
(when org-ref-label-debug
|
|
(message "ordl-1: removing %s" label))
|
|
(setq org-ref-labels
|
|
(cl-remove label org-ref-labels
|
|
:test 'string=))))
|
|
;; end is on a label, get it and remove it
|
|
(when (get-text-property end 'org-ref-label)
|
|
(let* ((start (previous-single-property-change end 'org-ref-label))
|
|
(end (next-single-property-change end 'org-ref-label))
|
|
(label (buffer-substring-no-properties start end)))
|
|
(when org-ref-label-debug (message "ordl-2: removing %s" label))
|
|
(setq org-ref-labels
|
|
(cl-remove label org-ref-labels
|
|
:test 'string=))))
|
|
;; finally if we delete more than 2 chars, scan the region to remove.
|
|
(when (> (- end start) 2)
|
|
(save-excursion
|
|
(save-match-data
|
|
(cl-loop for rx in org-ref-label-regexps
|
|
do
|
|
(goto-char start)
|
|
(while (re-search-forward rx end t)
|
|
(let ((label (match-string-no-properties 1)))
|
|
;; I don't know why this gets found, but some labels are
|
|
;; empty strings. we don't store these.
|
|
(when org-ref-label-debug (message "ordl-3: removing %s" label))
|
|
(setq org-ref-labels
|
|
(cl-remove label
|
|
org-ref-labels
|
|
:test 'string=)))))))))
|
|
|
|
;; this is an insertion. start=end
|
|
;; if the previous position is a label, we need to find it
|
|
(when (and
|
|
(not (eobp))
|
|
(> start 1)
|
|
(get-text-property (- start 1) 'org-ref-label))
|
|
(let ((label (buffer-substring
|
|
start
|
|
(previous-single-property-change (- start 1) 'org-ref-label))))
|
|
(when org-ref-label-debug (message "ordl-4: removing %s" label))
|
|
(setq org-ref-labels
|
|
(cl-remove label
|
|
org-ref-labels
|
|
:test 'string=)))))))
|
|
|
|
|
|
(defun org-ref-setup-label-finders ()
|
|
"Set up the functions for maintaining labels in a buffer."
|
|
(setq-local org-ref-labels '())
|
|
(org-ref-add-labels (point-min) (point-max))
|
|
(add-to-list 'jit-lock-functions 'org-ref-add-labels)
|
|
(add-to-list 'before-change-functions 'org-ref-delete-labels))
|
|
|
|
|
|
(add-hook 'org-mode-hook 'org-ref-setup-label-finders t)
|
|
|
|
|
|
(defun org-ref-get-labels ()
|
|
"Return a list of labels in the buffer that you can make a ref link to.
|
|
This is used to complete ref links and in `org-ref-ref-face-fn'.
|
|
Note the list is returned in reverse order as it was created by
|
|
font-lock. Initially, the labels start in order, but if you edit
|
|
the buffer in nonlinear ways, the labels may get out of order. I
|
|
don't know a good way to keep these in order. I tried a version
|
|
where I kept the pos of each one, but they change so it is hard
|
|
to keep them sorted.
|
|
|
|
If the `org-ref-labels' variable is empty, we try scanning the
|
|
whole buffer for them."
|
|
(when (or (null org-ref-labels) (null org-ref-label-use-font-lock))
|
|
(save-excursion
|
|
(org-ref-add-labels (point-min) (point-max))))
|
|
(reverse org-ref-labels))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-insert-ref-link ()
|
|
"Completion function for a ref link."
|
|
(interactive)
|
|
(insert (org-ref-complete-link)))
|
|
|
|
|
|
;;** pageref link
|
|
|
|
(org-ref-link-set-parameters "pageref"
|
|
:follow #'org-ref-ref-follow
|
|
:export (lambda (path desc format)
|
|
(cond
|
|
((eq format 'html) (format "(<pageref>%s</pageref>)" path))
|
|
((eq format 'latex)
|
|
(format "\\pageref{%s}" path))))
|
|
:face 'org-ref-ref-face-fn
|
|
:complete #'org-pageref-complete-link
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
|
|
(defun org-pageref-complete-link (&optional arg)
|
|
"Completion function for ref links.
|
|
Optional argument ARG Does nothing."
|
|
(let ((label))
|
|
(setq label (completing-read "label: " (org-ref-get-labels)))
|
|
(format "ref:%s" label)))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-pageref-insert-ref-link ()
|
|
"Insert a pageref link with completion."
|
|
(interactive)
|
|
(insert (org-pageref-complete-link)))
|
|
|
|
;;** nameref link
|
|
|
|
(defun org-ref-export-nameref (path desc format)
|
|
"Export function for nameref links."
|
|
(cond
|
|
((eq format 'html) (format "(<nameref>%s</nameref>)" path))
|
|
((eq format 'latex)
|
|
(format "\\nameref{%s}" path))))
|
|
|
|
|
|
(org-ref-link-set-parameters "nameref"
|
|
:follow #'org-ref-ref-follow
|
|
:export #'org-ref-export-nameref
|
|
:complete #'org-ref-complete-link
|
|
:face 'org-ref-ref-face-fn
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
;;** eqref link
|
|
|
|
(defun org-ref-eqref-export (keyword desc format)
|
|
(cond
|
|
((eq format 'latex) (format "\\eqref{%s}" keyword))
|
|
;;considering the fact that latex's the standard of math formulas, just use mathjax to render the html
|
|
;;customize the variable 'org-html-mathjax-template' and 'org-html-mathjax-options' refering to 'autonumber'
|
|
((eq format 'html) (format "\\eqref{%s}" keyword))
|
|
((eq format 'md)
|
|
(format "[%s](#%s)" keyword keyword))))
|
|
|
|
|
|
(org-ref-link-set-parameters "eqref"
|
|
:follow #'org-ref-ref-follow
|
|
:export #'org-ref-eqref-export
|
|
;; This isn't equation specific, one day we might try to make it that way.
|
|
:complete #'org-ref-complete-link
|
|
:face 'org-ref-ref-face-fn
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
;;** autoref link
|
|
|
|
(defun org-ref-autoref-export (keyword desc format)
|
|
"Autoref export function."
|
|
(cond
|
|
((eq format 'latex) (format "\\autoref{%s}" keyword))
|
|
;; considering the fact that latex's the standard of math formulas, just use
|
|
;;mathjax to render the html customize the variable
|
|
;;'org-html-mathjax-template' and 'org-html-mathjax-options' refering to
|
|
;;'autonumber'
|
|
((eq format 'html) (format "\\autoref{%s}" keyword))))
|
|
|
|
|
|
(org-ref-link-set-parameters "autoref"
|
|
:follow #'org-ref-ref-follow
|
|
:export #'org-ref-autoref-export
|
|
:complete #'org-ref-complete-link
|
|
:face 'org-ref-ref-face-fn
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
;;** cref link
|
|
;; for LaTeX cleveref package:
|
|
;; https://www.ctan.org/tex-archive/macros/latex/contrib/cleveref
|
|
|
|
(defun org-ref-cref-export (keyword desc format)
|
|
"cref link export function.
|
|
See https://www.ctan.org/tex-archive/macros/latex/contrib/cleveref"
|
|
(cond
|
|
((eq format 'latex) (format "\\cref{%s}" keyword))
|
|
;; considering the fact that latex's the standard of math formulas, just use
|
|
;;mathjax to render the html customize the variable
|
|
;;'org-html-mathjax-template' and 'org-html-mathjax-options' refering to
|
|
;;'autonumber'
|
|
((eq format 'html) (format "\\cref{%s}" keyword))))
|
|
|
|
|
|
(defun org-ref-Cref-export (keyword desc format)
|
|
"Cref link export function.
|
|
The capitalized version. See
|
|
https://www.ctan.org/tex-archive/macros/latex/contrib/cleveref"
|
|
(cond
|
|
((eq format 'latex) (format "\\Cref{%s}" keyword))
|
|
;; considering the fact that latex's the standard of math formulas, just use
|
|
;;mathjax to render the html customize the variable
|
|
;;'org-html-mathjax-template' and 'org-html-mathjax-options' refering to
|
|
;;'autonumber'
|
|
((eq format 'html) (format "\\Cref{%s}" keyword))))
|
|
|
|
|
|
(org-ref-link-set-parameters "cref"
|
|
:follow #'org-ref-ref-follow
|
|
:export #'org-ref-cref-export
|
|
:complete #'org-ref-complete-link
|
|
:face 'org-ref-ref-face-fn
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
|
|
(org-ref-link-set-parameters "Cref"
|
|
:follow #'org-ref-ref-follow
|
|
:export #'org-ref-Cref-export
|
|
:complete #'org-ref-complete-link
|
|
:face 'org-ref-ref-face-fn
|
|
:help-echo #'org-ref-ref-help-echo)
|
|
|
|
|
|
;;** cite link
|
|
|
|
(defun org-ref-get-bibtex-key-under-cursor ()
|
|
"Return key under the cursor in org-mode.
|
|
We search forward from point to get a comma, or the end of the link,
|
|
and then backwards to get a comma, or the beginning of the link. that
|
|
delimits the keyword we clicked on. We also strip the text
|
|
properties."
|
|
(let* ((object (org-element-context))
|
|
(link-string (org-element-property :path object)))
|
|
;; you may click on the part before the citations. here we make
|
|
;; sure to move to the beginning so you get the first citation.
|
|
(let ((cp (point)))
|
|
(goto-char (org-element-property :begin object))
|
|
(search-forward link-string (org-element-property :end object))
|
|
(goto-char (match-beginning 0))
|
|
;; check if we clicked before the path and move as needed.
|
|
(unless (< cp (point))
|
|
(goto-char cp)))
|
|
|
|
(if (not (org-element-property :contents-begin object))
|
|
;; this means no description in the link
|
|
(progn
|
|
;; we need the link path start and end
|
|
(let (link-string-beginning link-string-end)
|
|
(save-excursion
|
|
(goto-char (org-element-property :begin object))
|
|
(search-forward link-string nil nil 1)
|
|
(setq link-string-beginning (match-beginning 0))
|
|
(setq link-string-end (match-end 0)))
|
|
|
|
(let (key-beginning key-end)
|
|
;; The key is the text between commas, or the link boundaries
|
|
(save-excursion
|
|
(if (search-forward "," link-string-end t 1)
|
|
(setq key-end (- (match-end 0) 1)) ; we found a match
|
|
(setq key-end link-string-end))) ; no comma found so take the end
|
|
;; and backward to previous comma from point which defines the start character
|
|
(save-excursion
|
|
(if (search-backward "," link-string-beginning 1 1)
|
|
(setq key-beginning (+ (match-beginning 0) 1)) ; we found a match
|
|
(setq key-beginning link-string-beginning))) ; no match found
|
|
;; save the key we clicked on.
|
|
(let ((bibtex-key
|
|
(org-ref-strip-string
|
|
(buffer-substring key-beginning key-end))))
|
|
(set-text-properties 0 (length bibtex-key) nil bibtex-key)
|
|
bibtex-key))))
|
|
|
|
;; link with description and multiple keys
|
|
(if (and (org-element-property :contents-begin object)
|
|
(string-match "," link-string)
|
|
(equal (org-element-type object) 'link))
|
|
;; point is not on the link description
|
|
(if (not (>= (point) (org-element-property :contents-begin object)))
|
|
(let (link-string-beginning link-string-end)
|
|
(save-excursion
|
|
(goto-char (org-element-property :begin object))
|
|
(search-forward link-string nil t 1)
|
|
(setq link-string-beginning (match-beginning 0))
|
|
(setq link-string-end (match-end 0)))
|
|
|
|
(let (key-beginning key-end)
|
|
;; The key is the text between commas, or the link boundaries
|
|
(save-excursion
|
|
(if (search-forward "," link-string-end t 1)
|
|
(setq key-end (- (match-end 0) 1)) ; we found a match
|
|
(setq key-end link-string-end))) ; no comma found so take the end
|
|
;; and backward to previous comma from point which defines the start character
|
|
|
|
(save-excursion
|
|
(if (search-backward "," link-string-beginning 1 1)
|
|
(setq key-beginning (+ (match-beginning 0) 1)) ; we found a match
|
|
(setq key-beginning link-string-beginning))) ; no match found
|
|
;; save the key we clicked on.
|
|
(let ((bibtex-key
|
|
(org-ref-strip-string
|
|
(buffer-substring key-beginning key-end))))
|
|
(set-text-properties 0 (length bibtex-key) nil bibtex-key)
|
|
bibtex-key)))
|
|
;; point is on the link description, assume we want the
|
|
;; last key
|
|
(let ((last-key (replace-regexp-in-string "[a-zA-Z0-9_-]*," "" link-string)))
|
|
last-key))
|
|
;; link with description. assume only one key
|
|
link-string))))
|
|
|
|
|
|
(defun org-ref-find-bibliography ()
|
|
"Find the bibliography in the buffer.
|
|
This function sets and returns cite-bibliography-files, which is
|
|
a list of files either from }, internal bibliographies, from files in the
|
|
BIBINPUTS env var, and finally falling back to what the user has
|
|
set in `org-ref-default-bibliography'"
|
|
(catch 'result
|
|
;; If you call this in a bibtex file, assume we want this file
|
|
(when (and buffer-file-name (f-ext? buffer-file-name "bib"))
|
|
(throw 'result (setq org-ref-bibliography-files (list buffer-file-name))))
|
|
|
|
;; otherwise, check current file for a bibliography source
|
|
(save-excursion
|
|
(save-restriction
|
|
(widen)
|
|
(goto-char (point-min))
|
|
(setq org-ref-bibliography-files ())
|
|
|
|
;; look for org-ref bibliography or addbibresource links
|
|
(while (re-search-forward
|
|
;; This just searches for these strings, and then checks if it
|
|
;; is on a link. This is faster than parsing the org-file when
|
|
;; it gets large.
|
|
"\\(bibliography\\|addbibresource\\):"
|
|
nil t)
|
|
(let ((link (org-element-context)))
|
|
(when (and (eq (car link) 'link)
|
|
(or
|
|
(string= (org-element-property :type link) "bibliography")
|
|
(string= (org-element-property :type link) "addbibresource")))
|
|
(dolist (bibfile (org-ref-split-and-strip-string
|
|
(org-element-property :path link)))
|
|
(let ((bibf (org-ref-find-bibfile bibfile)))
|
|
(when bibf
|
|
(push bibf org-ref-bibliography-files)))))))
|
|
|
|
(when org-ref-bibliography-files
|
|
(throw 'result
|
|
(setq org-ref-bibliography-files
|
|
(nreverse (delete-dups org-ref-bibliography-files)))))
|
|
|
|
;; Try addbibresource as a latex command. It appears that reftex does
|
|
;; not do this correctly, it only finds the first one but there could be
|
|
;; many.
|
|
(goto-char (point-min))
|
|
(while (re-search-forward
|
|
"\\\\addbibresource{\\(.*\\)}"
|
|
nil t)
|
|
(push (match-string 1) org-ref-bibliography-files))
|
|
|
|
(when org-ref-bibliography-files
|
|
(throw 'result (setq org-ref-bibliography-files
|
|
(nreverse org-ref-bibliography-files))))
|
|
|
|
;; we did not find org-ref links. now look for latex links
|
|
(goto-char (point-min))
|
|
(setq org-ref-bibliography-files
|
|
(reftex-locate-bibliography-files default-directory))
|
|
(when org-ref-bibliography-files
|
|
(throw 'result org-ref-bibliography-files)))
|
|
|
|
|
|
;; we did not find anything. use defaults
|
|
(setq org-ref-bibliography-files org-ref-default-bibliography)))
|
|
|
|
|
|
;; set reftex-default-bibliography so we can search
|
|
(set (make-local-variable 'reftex-default-bibliography) org-ref-bibliography-files)
|
|
org-ref-bibliography-files)
|
|
|
|
|
|
(defun org-ref-key-in-file-p (key filename)
|
|
"Determine if the KEY is in the FILENAME."
|
|
(with-temp-buffer
|
|
(insert-file-contents filename)
|
|
(bibtex-set-dialect (parsebib-find-bibtex-dialect) t)
|
|
(bibtex-search-entry key)))
|
|
|
|
|
|
(defun org-ref-possible-bibfiles ()
|
|
"Make a unique list of possible bibliography files for completing-read"
|
|
(-uniq
|
|
(append
|
|
;; see if we should add it to a bib-file defined in the file
|
|
(org-ref-find-bibliography)
|
|
;; or any bib-files that exist in the current directory
|
|
(f-entries "." (lambda (f)
|
|
(and (not (string-match "#" f))
|
|
(f-ext? f "bib"))))
|
|
;; and last in the default bibliography
|
|
org-ref-default-bibliography)))
|
|
|
|
|
|
(defun org-ref-get-bibtex-key-and-file (&optional key)
|
|
"Return a a cons cell of (KEY . file) that KEY is in.
|
|
If no key is provided, get one under point."
|
|
(let ((org-ref-bibliography-files (org-ref-find-bibliography))
|
|
(file))
|
|
(unless key
|
|
(setq key (org-ref-get-bibtex-key-under-cursor)))
|
|
(setq file (catch 'result
|
|
(cl-loop for file in org-ref-bibliography-files do
|
|
(if (org-ref-key-in-file-p
|
|
key
|
|
(file-truename file))
|
|
(throw 'result file)))))
|
|
(cons key (when (stringp file) (substring-no-properties file)))))
|
|
|
|
|
|
;;*** Generation of the cite links
|
|
(defmacro org-ref-make-completion-function (type)
|
|
"Macro to make a link completion function for a link of TYPE."
|
|
`(defun ,(intern (format "org-%s-complete-link" type)) (&optional arg)
|
|
(format
|
|
"%s:%s"
|
|
,type
|
|
(completing-read
|
|
"bibtex key: "
|
|
(let ((bibtex-files (org-ref-find-bibliography)))
|
|
(bibtex-global-key-alist))))))
|
|
|
|
|
|
(defmacro org-ref-make-format-function (type)
|
|
"Macro to make a format function for a link of TYPE."
|
|
`(defun ,(intern (format "org-ref-format-%s" type)) (keyword desc format)
|
|
,(format "Formatting function for %s links.\n[[%s:KEYWORD][DESC]]
|
|
FORMAT is a symbol for the export backend.
|
|
Supported backends: 'html, 'latex, 'ascii, 'org, 'md, 'pandoc" type type)
|
|
(cond
|
|
((eq format 'org)
|
|
(mapconcat
|
|
(lambda (key)
|
|
(format "[[#%s][%s]]" key key))
|
|
(org-ref-split-and-strip-string keyword) ","))
|
|
|
|
((eq format 'ascii)
|
|
(concat "["
|
|
(mapconcat
|
|
(lambda (key)
|
|
(format "%s" key))
|
|
(org-ref-split-and-strip-string keyword) ",") "]"))
|
|
|
|
((eq format 'html)
|
|
(mapconcat
|
|
(lambda (key)
|
|
(format org-ref-ref-html key key))
|
|
(org-ref-split-and-strip-string keyword) ","))
|
|
|
|
((eq format 'latex)
|
|
(if (string= (substring ,type -1) "s")
|
|
;; biblatex format for multicite commands, which all end in s. These
|
|
;; are formated as \cites{key1}{key2}...
|
|
(concat "\\" ,type
|
|
(mapconcat (lambda (key) (format "{%s}" key))
|
|
(org-ref-split-and-strip-string keyword) ""))
|
|
;; bibtex format
|
|
(concat "\\" ,type
|
|
(when desc (org-ref-format-citation-description desc)) "{"
|
|
(mapconcat
|
|
(lambda (key) key)
|
|
(org-ref-split-and-strip-string keyword) ",")
|
|
"}")))
|
|
;; simple format for odt.
|
|
((eq format 'odt)
|
|
(format "[%s]" keyword))
|
|
|
|
((eq format 'md)
|
|
(mapconcat (lambda (key)
|
|
;; this is an html link that has an anchor to jump back to,
|
|
;; and links to the entry in the bibliography. Also contains
|
|
;; a tooltip.
|
|
(format "<sup id=\"%s\"><a href=\"#%s\" title=\"%s\">%s</a></sup>"
|
|
;; this makes an anchor to return to
|
|
(md5 key)
|
|
key
|
|
;; awful way to get a simple tooltip... I just need
|
|
;; a simple formatted string, but the default has
|
|
;; too much html stuff in it, and this needs to be
|
|
;; cleaned of quotes and stuff,
|
|
(let ((org-ref-bibliography-files (org-ref-find-bibliography))
|
|
(file) (entry) (bibtex-entry) (entry-type) (format)
|
|
(org-ref-bibliography-entry-format
|
|
'(("article" . "%a, %t, %j, v(%n), %p (%y).")
|
|
("book" . "%a, %t, %u (%y).")
|
|
("techreport" . "%a, %t, %i, %u (%y).")
|
|
("proceedings" . "%e, %t in %S, %u (%y).")
|
|
("inproceedings" . "%a, %t, %p, in %b, edited by %e, %u (%y)"))))
|
|
(setq file (catch 'result
|
|
(cl-loop for file in org-ref-bibliography-files do
|
|
(if (org-ref-key-in-file-p key (file-truename file))
|
|
(throw 'result file)
|
|
(message "%s not found in %s"
|
|
key (file-truename file))))))
|
|
|
|
(with-temp-buffer
|
|
(insert-file-contents file)
|
|
(bibtex-set-dialect (parsebib-find-bibtex-dialect) t)
|
|
(bibtex-search-entry key nil 0)
|
|
(setq bibtex-entry (bibtex-parse-entry))
|
|
;; downcase field names so they work in the format-citation code
|
|
(dolist (cons-cell bibtex-entry)
|
|
(setf (car cons-cell) (downcase (car cons-cell))))
|
|
(setq entry-type (downcase (cdr (assoc "=type=" bibtex-entry))))
|
|
|
|
(setq format (cdr (assoc entry-type org-ref-bibliography-entry-format)))
|
|
(if format
|
|
(setq entry (org-ref-reftex-format-citation bibtex-entry format))
|
|
;; if no format, we use the bibtex entry itself as a fallback
|
|
(save-restriction
|
|
(bibtex-narrow-to-entry)
|
|
(setq entry (buffer-string)))))
|
|
(replace-regexp-in-string "\"" "" (htmlize-escape-or-link entry)))
|
|
key))
|
|
(s-split "," keyword) "<sup>,</sup>"))
|
|
;; for pandoc we generate pandoc citations
|
|
((eq format 'pandoc)
|
|
(cond
|
|
(desc ;; pre and or post text
|
|
(let* ((text (split-string desc "::"))
|
|
(pre (car text))
|
|
(post (cadr text)))
|
|
(concat
|
|
(format "[@%s," keyword)
|
|
(when pre (format " %s" pre))
|
|
(when post (format ", %s" post))
|
|
"]")))
|
|
(t
|
|
(format "[%s]"
|
|
(mapconcat
|
|
(lambda (key) (concat "@" key))
|
|
(org-ref-split-and-strip-string keyword)
|
|
"; "))))))))
|
|
|
|
|
|
(defun org-ref-format-citation-description (desc)
|
|
"Return formatted citation description.
|
|
If the cite link has a DESC (description), it is optional text
|
|
for the citation command. You can specify pre and post text by
|
|
separating these with ::, for example [[cite:key][pre text::post
|
|
text]]."
|
|
(cond
|
|
((string-match "::" desc)
|
|
(let ((results (split-string desc "::")))
|
|
(format "[%s][%s]" (nth 0 results) (nth 1 results))))
|
|
(t (format "[%s]" desc))))
|
|
|
|
|
|
(defun org-ref-bibtex-store-link ()
|
|
"Store a link from a bibtex file. Only supports the cite link.
|
|
This essentially the same as the store link in org-bibtex, but it
|
|
creates a cite link."
|
|
(when (eq major-mode 'bibtex-mode)
|
|
(let* ((entry (mapcar
|
|
;; repair strings enclosed in "..." or {...}
|
|
(lambda(c)
|
|
(if (string-match
|
|
"^\\(?:{\\|\"\\)\\(.*\\)\\(?:}\\|\"\\)$" (cdr c))
|
|
(cons (car c) (match-string 1 (cdr c))) c))
|
|
(save-excursion
|
|
(bibtex-beginning-of-entry)
|
|
(bibtex-parse-entry))))
|
|
(link (concat "cite:" (cdr (assoc "=key=" entry)))))
|
|
(org-store-link-props
|
|
:key (cdr (assoc "=key=" entry))
|
|
:author (or (cdr (assoc "author" entry)) "[no author]")
|
|
:editor (or (cdr (assoc "editor" entry)) "[no editor]")
|
|
:title (or (cdr (assoc "title" entry)) "[no title]")
|
|
:booktitle (or (cdr (assoc "booktitle" entry)) "[no booktitle]")
|
|
:journal (or (cdr (assoc "journal" entry)) "[no journal]")
|
|
:publisher (or (cdr (assoc "publisher" entry)) "[no publisher]")
|
|
:pages (or (cdr (assoc "pages" entry)) "[no pages]")
|
|
:url (or (cdr (assoc "url" entry)) "[no url]")
|
|
:year (or (cdr (assoc "year" entry)) "[no year]")
|
|
:month (or (cdr (assoc "month" entry)) "[no month]")
|
|
:address (or (cdr (assoc "address" entry)) "[no address]")
|
|
:volume (or (cdr (assoc "volume" entry)) "[no volume]")
|
|
:number (or (cdr (assoc "number" entry)) "[no number]")
|
|
:annote (or (cdr (assoc "annote" entry)) "[no annotation]")
|
|
:series (or (cdr (assoc "series" entry)) "[no series]")
|
|
:abstract (or (cdr (assoc "abstract" entry)) "[no abstract]")
|
|
:btype (or (cdr (assoc "=type=" entry)) "[no type]")
|
|
:type "bibtex"
|
|
:link link
|
|
:description (let ((bibtex-autokey-names 1)
|
|
(bibtex-autokey-names-stretch 1)
|
|
(bibtex-autokey-name-case-convert-function 'identity)
|
|
(bibtex-autokey-name-separator " & ")
|
|
(bibtex-autokey-additional-names " et al.")
|
|
(bibtex-autokey-year-length 4)
|
|
(bibtex-autokey-name-year-separator " ")
|
|
(bibtex-autokey-titlewords 3)
|
|
(bibtex-autokey-titleword-separator " ")
|
|
(bibtex-autokey-titleword-case-convert-function 'identity)
|
|
(bibtex-autokey-titleword-length 'infty)
|
|
(bibtex-autokey-year-title-separator ": "))
|
|
(setq org-bibtex-description (bibtex-generate-autokey)))))))
|
|
|
|
|
|
;; This suppresses showing the warning buffer. helm-bibtex seems to make this
|
|
;; pop up in an irritating way.
|
|
(unless (boundp 'warning-suppress-types)
|
|
(require 'warnings))
|
|
|
|
|
|
(add-to-list 'warning-suppress-types '(:warning))
|
|
|
|
|
|
(defvar org-ref-buffer-hacked nil
|
|
"If non-nil this buffer has already been hacked and we don't need to do it again.
|
|
I use this so we only hack the variables once. This was added
|
|
because when you have local file/directory variables, it seems
|
|
like they don't get defined when font-lock is occurring, and it
|
|
results in warnings from `bibtex-completion' because it cannot
|
|
find the keys in the bibliographies. Doing this hack and the one
|
|
in `org-ref-cite-link-face-fn' makes the warnings go away. It
|
|
seems hacky, but the functions that fix it start with hack
|
|
so...")
|
|
|
|
(make-variable-buffer-local 'org-ref-buffer-hacked)
|
|
|
|
(defun org-ref-cite-link-face-fn (keys)
|
|
"Return a face for a cite link.
|
|
KEYS may be a comma-separated list of keys.
|
|
This is not smart enough yet to only highlight the bad key. If any key is bad, the whole cite will be red."
|
|
(unless org-ref-buffer-hacked
|
|
(hack-dir-local-variables)
|
|
(hack-local-variables-apply)
|
|
(setq org-ref-buffer-hacked t))
|
|
|
|
(save-match-data
|
|
(cond
|
|
((or (not org-ref-show-broken-links)
|
|
(let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
|
|
(-every?
|
|
'identity
|
|
(mapcar
|
|
(lambda (key)
|
|
(assoc "=key="
|
|
(bibtex-completion-get-entry key)))
|
|
(split-string keys ",")))))
|
|
'org-ref-cite-face)
|
|
(t
|
|
'font-lock-warning-face))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-define-citation-link (type &optional key)
|
|
"Add a citation link of TYPE for `org-ref'.
|
|
With optional KEY, set the reftex binding. For example:
|
|
\(org-ref-define-citation-link \"citez\" ?z) will create a new
|
|
citez link, with reftex key of z, and the completion function."
|
|
(interactive "sCitation Type: \ncKey: ")
|
|
|
|
;; create the formatting function
|
|
(eval `(org-ref-make-format-function ,type))
|
|
|
|
(eval
|
|
`(if (fboundp 'org-link-set-parameters)
|
|
(org-link-set-parameters
|
|
,type
|
|
:follow (lambda (_) (funcall org-ref-cite-onclick-function nil))
|
|
:export (quote ,(intern (format "org-ref-format-%s" type)))
|
|
:complete (quote ,(intern (format "org-%s-complete-link" type)))
|
|
:help-echo (lambda (window object position)
|
|
(when org-ref-show-citation-on-enter
|
|
(save-excursion
|
|
(goto-char position)
|
|
;; Here we wrap the citation string to a reasonable size.
|
|
(let ((s (org-ref-format-entry
|
|
(org-ref-get-bibtex-key-under-cursor))))
|
|
(with-temp-buffer
|
|
(insert s)
|
|
(fill-paragraph)
|
|
(buffer-string))))))
|
|
:face 'org-ref-cite-link-face-fn
|
|
:display 'full
|
|
:keymap org-ref-cite-keymap)
|
|
(org-add-link-type
|
|
,type
|
|
(lambda (_path) (funcall org-ref-cite-onclick-function nil))
|
|
(quote ,(intern (format "org-ref-format-%s" type))))))
|
|
|
|
;; create the completion function
|
|
(eval `(org-ref-make-completion-function ,type))
|
|
|
|
;; store new type so it works with adding citations, which checks
|
|
;; for existence in this list
|
|
(add-to-list 'org-ref-cite-types type)
|
|
|
|
(unless (assoc 'org reftex-cite-format-builtin)
|
|
(add-to-list 'reftex-cite-format-builtin '(org "org-ref citations" ())))
|
|
|
|
;; and finally if a key is specified, we modify the reftex menu
|
|
(when key
|
|
(setf (nth 2 (assoc 'org reftex-cite-format-builtin))
|
|
(append (nth 2 (assoc 'org reftex-cite-format-builtin))
|
|
`((,key . ,(concat type ":%l")))))))
|
|
|
|
|
|
(defun org-ref-generate-cite-links ()
|
|
"Create all the link types and their completion functions."
|
|
(interactive)
|
|
(dolist (type org-ref-cite-types)
|
|
(org-ref-define-citation-link type))
|
|
(when (fboundp 'org-link-set-parameters)
|
|
(org-link-set-parameters "cite" :store #'org-ref-bibtex-store-link)))
|
|
|
|
|
|
;; This is what actually generated the cite links
|
|
(org-ref-generate-cite-links)
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-insert-cite-with-completion (type)
|
|
"Insert a cite link of TYPE with completion."
|
|
(interactive (list (completing-read "Type: " org-ref-cite-types)))
|
|
(insert (funcall (intern (format "org-%s-complete-link" type)))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-store-bibtex-entry-link ()
|
|
"Save a citation link to the current bibtex entry.
|
|
Save in the default link type."
|
|
(interactive)
|
|
(let ((link (concat org-ref-default-citation-link
|
|
":"
|
|
(save-excursion
|
|
(bibtex-beginning-of-entry)
|
|
(reftex-get-bib-field
|
|
"=key=" (bibtex-parse-entry))))))
|
|
(message "saved %s" link)
|
|
(push (list link) org-stored-links)
|
|
(car org-stored-links)))
|
|
|
|
;;* Index link
|
|
(org-ref-link-set-parameters "index"
|
|
:follow (lambda (path)
|
|
(occur path))
|
|
:export (lambda (path desc format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\index{%s}" path)))))
|
|
|
|
|
|
;; this will generate a temporary index of entries in the file when clicked on.
|
|
;;;###autoload
|
|
(defun org-ref-index (&optional path)
|
|
"Open an *index* buffer with links to index entries.
|
|
PATH is required for the org-link, but it does nothing here."
|
|
(interactive)
|
|
(let ((*index-links* '())
|
|
(*initial-letters* '()))
|
|
|
|
;; get links
|
|
(org-element-map (org-ref-parse-buffer) 'link
|
|
(lambda (link)
|
|
(let ((type (nth 0 link))
|
|
(plist (nth 1 link)))
|
|
|
|
(when (equal (plist-get plist ':type) "index")
|
|
(add-to-list
|
|
'*index-links*
|
|
(cons (plist-get plist :path)
|
|
(format
|
|
"[[elisp:(progn (switch-to-buffer \"%s\") (goto-char %s) (org-cycle '(64)))][%s]] "
|
|
(current-buffer)
|
|
(plist-get plist :begin) ;; position of link
|
|
;; grab a description
|
|
(save-excursion
|
|
(goto-char (plist-get plist :begin))
|
|
(if (thing-at-point 'sentence)
|
|
;; get a sentence
|
|
(let ((s (thing-at-point 'sentence)))
|
|
(cl-loop for char in '("[" "]" "\n")
|
|
do
|
|
(setq s (replace-regexp-in-string
|
|
(regexp-quote char) " " s)))
|
|
(concat s " "))
|
|
;; or call it a link
|
|
"link")))))))))
|
|
|
|
;; sort the links
|
|
(setq *index-links* (cl-sort *index-links* 'string-lessp :key 'car))
|
|
|
|
;; now separate out into chunks first letters
|
|
(dolist (link *index-links*)
|
|
(add-to-list '*initial-letters* (substring (car link) 0 1) t))
|
|
|
|
;; now create the index
|
|
(switch-to-buffer (get-buffer-create "*index*"))
|
|
(org-mode)
|
|
(erase-buffer)
|
|
(insert "#+TITLE: Index\n\n")
|
|
(dolist (letter *initial-letters*)
|
|
(insert (format "* %s\n" (upcase letter)))
|
|
;; now process the links
|
|
(while (and
|
|
*index-links*
|
|
(string= letter (substring (car (car *index-links*)) 0 1)))
|
|
(let ((link (pop *index-links*)))
|
|
(insert (format "%s %s\n\n" (car link) (cdr link))))))
|
|
(switch-to-buffer "*index*")))
|
|
|
|
|
|
(org-ref-link-set-parameters "printindex"
|
|
:follow #'org-ref-index
|
|
:export (lambda (path desc format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\printindex")))))
|
|
|
|
|
|
;;* Utilities
|
|
;;** create text citations from a bibtex entry
|
|
|
|
(defun org-ref-bib-citation ()
|
|
"From a bibtex entry, create and return a citation string.
|
|
If `bibtex-completion' library is loaded, return reference in APA
|
|
format. Otherwise return a citation string from `org-ref-get-bibtex-entry-citation'."
|
|
(save-excursion
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((bibtex-expand-strings t)
|
|
(entry (bibtex-parse-entry t))
|
|
(key (reftex-get-bib-field "=key=" entry)))
|
|
(org-ref-format-entry key))))
|
|
|
|
|
|
;;** Open pdf in bibtex entry
|
|
;;;###autoload
|
|
(defun org-ref-open-bibtex-pdf ()
|
|
"Open pdf for a bibtex entry, if it exists.
|
|
assumes point is in
|
|
the entry of interest in the bibfile. but does not check that."
|
|
(interactive)
|
|
(save-excursion
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((bibtex-expand-strings t)
|
|
(entry (bibtex-parse-entry t))
|
|
(key (reftex-get-bib-field "=key=" entry))
|
|
(pdf (funcall org-ref-get-pdf-filename-function key)))
|
|
(if (file-exists-p pdf)
|
|
(org-open-link-from-string (format "[[file:%s]]" pdf))
|
|
(ding)))))
|
|
|
|
(defun org-ref-notes-function-one-file (thekey)
|
|
"Function to open note belonging to THEKEY.
|
|
|
|
Set `org-ref-notes-function' to this function if you use one
|
|
long file with headlines for each entry."
|
|
(let*
|
|
((results
|
|
(org-ref-get-bibtex-key-and-file thekey))
|
|
(key
|
|
(car results))
|
|
(bibfile
|
|
(cdr results)))
|
|
(with-temp-buffer
|
|
(insert-file-contents bibfile)
|
|
(bibtex-set-dialect
|
|
(parsebib-find-bibtex-dialect)
|
|
t)
|
|
(bibtex-search-entry key)
|
|
(org-ref-open-bibtex-notes))))
|
|
|
|
(defun org-ref-notes-function-many-files (thekey)
|
|
"Function to open note belonging to THEKEY.
|
|
|
|
Set `org-ref-notes-function' to this function if you use one file
|
|
for each bib entry."
|
|
(let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
|
|
(bibtex-completion-edit-notes
|
|
(list (car (org-ref-get-bibtex-key-and-file thekey))))))
|
|
|
|
;;** Open notes from bibtex entry
|
|
;;;###autoload
|
|
(defun org-ref-open-bibtex-notes ()
|
|
"From a bibtex entry, open the notes if they exist.
|
|
If the notes do not exist, then create a heading.
|
|
|
|
I never did figure out how to use reftex to make this happen
|
|
non-interactively. the `reftex-format-citation' function did not
|
|
work perfectly; there were carriage returns in the strings, and
|
|
it did not put the key where it needed to be. so, below I replace
|
|
the carriage returns and extra spaces with a single space and
|
|
construct the heading by hand."
|
|
(interactive)
|
|
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((cb (current-buffer))
|
|
(bibtex-expand-strings t)
|
|
(entry (cl-loop for (key . value) in (bibtex-parse-entry t)
|
|
collect (cons (downcase key) (s-collapse-whitespace value))))
|
|
(key (reftex-get-bib-field "=key=" entry)))
|
|
|
|
;; save key to clipboard to make saving pdf later easier by pasting.
|
|
(with-temp-buffer
|
|
(insert key)
|
|
(kill-ring-save (point-min) (point-max)))
|
|
|
|
;; now look for entry in the notes file
|
|
(save-restriction
|
|
(if org-ref-bibliography-notes
|
|
(find-file-other-window org-ref-bibliography-notes)
|
|
(error "Org-ref-bib-bibliography-notes is not set to anything"))
|
|
|
|
(widen)
|
|
(goto-char (point-min))
|
|
(let* ((headlines (org-element-map
|
|
(org-ref-parse-buffer)
|
|
'headline 'identity))
|
|
(keys (mapcar
|
|
(lambda (hl) (org-element-property :CUSTOM_ID hl))
|
|
headlines)))
|
|
;; put new entry in notes if we don't find it.
|
|
(if (-contains? keys key)
|
|
(progn
|
|
(org-open-link-from-string (format "[[#%s]]" key))
|
|
(funcall org-ref-open-notes-function))
|
|
;; no entry found, so add one
|
|
(goto-char (point-max))
|
|
(insert (org-ref-reftex-format-citation
|
|
entry (concat "\n" org-ref-note-title-format)))
|
|
(mapc (lambda (x)
|
|
(save-restriction
|
|
(save-excursion
|
|
(funcall x))))
|
|
org-ref-create-notes-hook)
|
|
(save-buffer))))))
|
|
|
|
|
|
;;** Open bibtex entry in browser
|
|
;;;###autoload
|
|
(defun org-ref-open-in-browser ()
|
|
"Open the bibtex entry at point in a browser using the url field or doi field."
|
|
(interactive)
|
|
(save-excursion
|
|
(bibtex-beginning-of-entry)
|
|
(catch 'done
|
|
(let ((url (bibtex-autokey-get-field "url")))
|
|
(when url
|
|
(browse-url url)
|
|
(throw 'done nil)))
|
|
|
|
(let ((doi (bibtex-autokey-get-field "doi")))
|
|
(when doi
|
|
(if (string-match "^http" doi)
|
|
(browse-url doi)
|
|
(browse-url (format "http://dx.doi.org/%s" doi)))
|
|
(throw 'done nil)))
|
|
(message "No url or doi found"))))
|
|
|
|
|
|
;;** Build a pdf of the bibtex file
|
|
;;;###autoload
|
|
(defun org-ref-build-full-bibliography ()
|
|
"Build pdf of all bibtex entries, and open it."
|
|
(interactive)
|
|
(let* ((bibfile (file-name-nondirectory (buffer-file-name)))
|
|
(bib-base (file-name-sans-extension bibfile))
|
|
(texfile (concat bib-base ".tex"))
|
|
(pdffile (concat bib-base ".pdf")))
|
|
(find-file texfile)
|
|
(erase-buffer)
|
|
(insert (format "\\documentclass[12pt]{article}
|
|
\\usepackage[version=3]{mhchem}
|
|
\\usepackage{url}
|
|
\\usepackage[numbers]{natbib}
|
|
\\usepackage[colorlinks=true, linkcolor=blue, urlcolor=blue, pdfstartview=FitH]{hyperref}
|
|
\\usepackage{doi}
|
|
\\begin{document}
|
|
\\nocite{*}
|
|
\\bibliographystyle{unsrtnat}
|
|
\\bibliography{%s}
|
|
\\end{document}" bib-base))
|
|
(save-buffer)
|
|
(shell-command (concat "pdflatex " bib-base))
|
|
(shell-command (concat "bibtex " bib-base))
|
|
(shell-command (concat "pdflatex " bib-base))
|
|
(shell-command (concat "pdflatex " bib-base))
|
|
(kill-buffer texfile)
|
|
(org-open-file pdffile)))
|
|
|
|
|
|
;;** Extract bibtex entries in org-file
|
|
|
|
;;;###autoload
|
|
(defun org-ref-extract-bibtex-entries ()
|
|
"Extract the bibtex entries in the current buffer into a bibtex src block."
|
|
(interactive)
|
|
(let* ((bibtex-files (org-ref-find-bibliography))
|
|
(keys (reverse (org-ref-get-bibtex-keys)))
|
|
(bibtex-entry-kill-ring-max (length keys))
|
|
(bibtex-entry-kill-ring '()))
|
|
|
|
(save-window-excursion
|
|
(cl-loop for key in keys
|
|
do
|
|
(bibtex-search-entry key t)
|
|
(bibtex-kill-entry t)))
|
|
|
|
(goto-char (point-max))
|
|
(insert "\n\n")
|
|
(org-insert-heading)
|
|
(insert (format " Bibtex entries
|
|
|
|
#+BEGIN_SRC bibtex :tangle %s
|
|
%s
|
|
#+END_SRC"
|
|
(let ((bibfile (concat (file-name-base
|
|
(or (buffer-file-name) "references"))
|
|
".bib")))
|
|
(if (file-exists-p bibfile)
|
|
(file-name-nondirectory
|
|
(read-file-name "Bibfile: " nil nil nil bibfile))
|
|
bibfile))
|
|
(mapconcat
|
|
'identity
|
|
bibtex-entry-kill-ring
|
|
"\n\n")))))
|
|
|
|
;;;###autoload
|
|
(defun org-ref-extract-bibtex-to-file (bibfile &optional clobber)
|
|
"Extract all bibtex entries for citations buffer to BIBFILE.
|
|
If BIBFILE exists, append, unless you use a prefix arg (C-u),
|
|
which will CLOBBER the file."
|
|
(interactive
|
|
(list (read-file-name "Bibfile: " nil nil nil
|
|
(file-name-nondirectory
|
|
(concat (file-name-sans-extension
|
|
(buffer-file-name))
|
|
".bib")))
|
|
current-prefix-arg))
|
|
|
|
(let* ((bibtex-files (org-ref-find-bibliography))
|
|
(keys (reverse (org-ref-get-bibtex-keys)))
|
|
(bibtex-entry-kill-ring-max (length keys))
|
|
(bibtex-entry-kill-ring '())
|
|
(kill-cb (not (find-buffer-visiting bibfile)))
|
|
(cb (find-file-noselect bibfile))
|
|
(current-bib-entries (with-current-buffer cb
|
|
(prog1
|
|
(buffer-string)
|
|
(when kill-cb (kill-buffer cb))))))
|
|
|
|
(save-window-excursion
|
|
(cl-loop for key in keys
|
|
do
|
|
(bibtex-search-entry key t)
|
|
(bibtex-kill-entry t)))
|
|
|
|
(with-temp-file bibfile
|
|
(unless clobber (insert current-bib-entries))
|
|
(insert (mapconcat
|
|
'identity
|
|
bibtex-entry-kill-ring
|
|
"\n\n")))))
|
|
|
|
|
|
;;** Find bad citations
|
|
(defun org-ref-list-index (substring list)
|
|
"Return the index of SUBSTRING in a LIST of strings."
|
|
(let ((i 0)
|
|
(found nil))
|
|
(dolist (arg list i)
|
|
(if (string-match (concat "^" substring "$") arg)
|
|
(progn
|
|
(setq found t)
|
|
(cl-return i)))
|
|
(setq i (+ i 1)))
|
|
;; return counter if found, otherwise return nil
|
|
(if found i nil)))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-find-bad-citations ()
|
|
"Create a list of citation keys that do not have a matching bibtex entry.
|
|
List is displayed in an `org-mode' buffer using the known bibtex
|
|
file. Makes a new buffer with clickable links."
|
|
(interactive)
|
|
;; generate the list of bibtex-keys and cited keys
|
|
(let* ((bibtex-files (mapcar
|
|
'file-name-nondirectory
|
|
(org-ref-find-bibliography)))
|
|
(bibtex-file-path (mapconcat
|
|
(lambda (x)
|
|
(file-name-directory (file-truename x)))
|
|
(org-ref-find-bibliography)
|
|
":"))
|
|
(bibtex-keys (mapcar (lambda (x)
|
|
(car x))
|
|
(bibtex-global-key-alist)))
|
|
(bad-citations '()))
|
|
|
|
(org-element-map (org-ref-parse-buffer) 'link
|
|
(lambda (link)
|
|
(let ((plist (nth 1 link)))
|
|
(when (-contains? org-ref-cite-types
|
|
(plist-get plist :type))
|
|
(dolist (key (org-ref-split-and-strip-string
|
|
(plist-get plist :path)))
|
|
(when (not (org-ref-list-index key bibtex-keys))
|
|
(setq
|
|
bad-citations
|
|
(append
|
|
bad-citations
|
|
`(,(format "%s [[elisp:(progn (switch-to-buffer-other-frame \"%s\")(goto-char %s))][not found here]]\n"
|
|
key
|
|
(buffer-name)
|
|
(plist-get plist :begin))))))))))
|
|
;; set with-affilates to t to get citations in a caption
|
|
nil nil nil t)
|
|
|
|
(if bad-citations
|
|
(progn
|
|
(switch-to-buffer-other-window "*Missing citations*")
|
|
(org-mode)
|
|
(erase-buffer)
|
|
(insert "* List of bad cite links\n")
|
|
(insert (mapconcat 'identity bad-citations ""))
|
|
(use-local-map (copy-keymap org-mode-map))
|
|
(local-set-key "q" #'(lambda () (interactive) (kill-buffer))))
|
|
|
|
(when (get-buffer "*Missing citations*")
|
|
(kill-buffer "*Missing citations*"))
|
|
(message "No bad cite links found"))))
|
|
|
|
|
|
;;** bad citations, labels, refs and files in orgfile
|
|
(defun org-ref-bad-cite-candidates ()
|
|
"Return a list of conses (key . marker) where key does not exist in the known bibliography files, and marker points to the key."
|
|
(let* ((cp (point)) ; save to return to later
|
|
(bibtex-files (cl-loop for f in (org-ref-find-bibliography)
|
|
if (file-exists-p f)
|
|
collect (file-truename f)))
|
|
(bibtex-file-path (mapconcat
|
|
(lambda (x)
|
|
(file-name-directory (file-truename x)))
|
|
bibtex-files ":"))
|
|
(bibtex-keys (mapcar (lambda (x) (car x))
|
|
(bibtex-global-key-alist)))
|
|
(bad-citations '()))
|
|
|
|
(org-element-map (org-ref-parse-buffer) 'link
|
|
(lambda (link)
|
|
(let ((plist (nth 1 link)))
|
|
(when (-contains? org-ref-cite-types (plist-get plist :type))
|
|
(dolist (key (org-ref-split-and-strip-string
|
|
(plist-get plist :path)))
|
|
(when (not (org-ref-list-index key bibtex-keys))
|
|
(goto-char (plist-get plist :begin))
|
|
(re-search-forward key)
|
|
(push (cons key (point-marker)) bad-citations))))))
|
|
;; add with-affiliates to get cites in caption
|
|
nil nil nil t)
|
|
(goto-char cp)
|
|
bad-citations))
|
|
|
|
|
|
(defun org-ref-bad-ref-candidates ()
|
|
"Return a list of conses (ref . marker) where ref is a ref link that does not point to anything (i.e. a label)."
|
|
;; first get a list of legitimate labels
|
|
(let ((cp (point))
|
|
(labels (org-ref-get-labels))
|
|
(bad-refs '()))
|
|
;; now loop over ref links
|
|
(goto-char (point-min))
|
|
(org-element-map (org-ref-parse-buffer) 'link
|
|
(lambda (link)
|
|
(let ((plist (nth 1 link)))
|
|
(when (or (equal (plist-get plist ':type) "ref")
|
|
(equal (plist-get plist ':type) "eqref")
|
|
(equal (plist-get plist ':type) "pageref")
|
|
(equal (plist-get plist ':type) "nameref")
|
|
(equal (plist-get plist ':type) "autoref")
|
|
(equal (plist-get plist ':type) "cref")
|
|
(equal (plist-get plist ':type) "Cref"))
|
|
(unless (-contains? labels (plist-get plist :path))
|
|
(goto-char (plist-get plist :begin))
|
|
(add-to-list
|
|
'bad-refs
|
|
(cons (plist-get plist :path)
|
|
(point-marker))))))))
|
|
(goto-char cp)
|
|
bad-refs))
|
|
|
|
|
|
(defun org-ref-bad-label-candidates ()
|
|
"Return a list of labels where label is multiply defined."
|
|
(let ((labels (org-ref-get-labels))
|
|
(multiple-labels '()))
|
|
;; labels should be a unique list.
|
|
(dolist (label labels)
|
|
(when (> (org-ref-count-labels label) 1)
|
|
(let ((cp (point)))
|
|
(goto-char (point-min))
|
|
;; regular org label:tag links
|
|
(while (re-search-forward
|
|
(format "[^#+]label:%s\\s-" label) nil t)
|
|
(cl-pushnew (cons label (point-marker)) multiple-labels
|
|
:test (lambda (a b)
|
|
(and (string= (car a) (car b))
|
|
(= (marker-position (cdr a))
|
|
(marker-position (cdr b)))))))
|
|
|
|
(goto-char (point-min))
|
|
;; latex style
|
|
(while (re-search-forward
|
|
(format "\\label{%s}\\s-?" label) nil t)
|
|
(cl-pushnew (cons label (point-marker)) multiple-labels
|
|
:test (lambda (a b)
|
|
(and (string= (car a) (car b))
|
|
(= (marker-position (cdr a))
|
|
(marker-position (cdr b)))))))
|
|
|
|
;; keyword style
|
|
(goto-char (point-min))
|
|
(while (re-search-forward
|
|
(format "^\\( \\)*#\\+label:\\s-*%s" label) nil t)
|
|
(cl-pushnew (cons label (point-marker)) multiple-labels
|
|
:test (lambda (a b)
|
|
(and (string= (car a) (car b))
|
|
(= (marker-position (cdr a))
|
|
(marker-position (cdr b)))))))
|
|
|
|
(goto-char (point-min))
|
|
(while (re-search-forward
|
|
(format "^\\( \\)*#\\+tblname:\\s-*%s" label) nil t)
|
|
(cl-pushnew (cons label (point-marker)) multiple-labels
|
|
:test (lambda (a b)
|
|
(and (string= (car a) (car b))
|
|
(= (marker-position (cdr a))
|
|
(marker-position (cdr b)))))))
|
|
(goto-char cp))))
|
|
multiple-labels))
|
|
|
|
|
|
(defun org-ref-bad-file-link-candidates ()
|
|
"Return list of conses (link . marker) where the file in the link does not exist."
|
|
(let* ((bad-files '()))
|
|
(org-element-map (org-ref-parse-buffer) 'link
|
|
(lambda (link)
|
|
(let ((type (org-element-property :type link)))
|
|
(when (or (string= "file" type)
|
|
(string= "attachfile" type))
|
|
(unless (file-exists-p (org-element-property :path link))
|
|
(add-to-list 'bad-files
|
|
(cons (org-element-property :path link)
|
|
(save-excursion
|
|
(goto-char
|
|
(org-element-property :begin link))
|
|
(point-marker)))))))))
|
|
;; Let us also check \attachfile{fname}
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(while (re-search-forward "\\\\attachfile{\\([^}]*\\)}" nil t)
|
|
(unless (file-exists-p (match-string 1))
|
|
(add-to-list 'bad-files (cons (match-string 1) (point-marker))))))
|
|
bad-files))
|
|
|
|
|
|
|
|
;;** Find non-ascii charaters
|
|
;;;###autoload
|
|
(defun org-ref-find-non-ascii-characters ()
|
|
"Find non-ascii characters in the buffer. Useful for cleaning up bibtex files."
|
|
(interactive)
|
|
(occur "[^[:ascii:]]"))
|
|
|
|
|
|
;;** Sort fields in a bibtex entry
|
|
;;;###autoload
|
|
(defun org-ref-sort-bibtex-entry ()
|
|
"Sort fields of entry in standard order."
|
|
(interactive)
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((entry (bibtex-parse-entry))
|
|
(entry-fields)
|
|
(other-fields)
|
|
(type (cdr (assoc "=type=" entry)))
|
|
(key (cdr (assoc "=key=" entry)))
|
|
(field-order (cdr (assoc (if type (downcase type))
|
|
org-ref-bibtex-sort-order))))
|
|
|
|
;; these are the fields we want to order that are in this entry
|
|
(setq entry-fields (mapcar (lambda (x) (car x)) entry))
|
|
;; we do not want to reenter these fields
|
|
(setq entry-fields (remove "=key=" entry-fields))
|
|
(setq entry-fields (remove "=type=" entry-fields))
|
|
|
|
;;these are the other fields in the entry, and we sort them alphabetically.
|
|
(setq other-fields
|
|
(sort (-remove (lambda(x) (member x field-order)) entry-fields)
|
|
'string<))
|
|
|
|
(save-restriction
|
|
(bibtex-kill-entry)
|
|
(insert
|
|
(concat "@" type "{" key ",\n"
|
|
(mapconcat
|
|
(lambda (field)
|
|
(when (member field entry-fields)
|
|
(format "%s = %s,"
|
|
field
|
|
(cdr (assoc field entry)))))
|
|
field-order "\n")
|
|
;; now add the other fields
|
|
(mapconcat
|
|
(lambda (field)
|
|
(cl-loop for (f . v) in entry concat
|
|
(when (string= f field)
|
|
(format "%s = %s,\n" f v))))
|
|
(-uniq other-fields) "\n")
|
|
"\n}"))
|
|
(bibtex-find-entry key)
|
|
(bibtex-fill-entry)
|
|
(bibtex-clean-entry))))
|
|
|
|
;; downcase entries
|
|
;;;###autoload
|
|
(defun org-ref-downcase-bibtex-entry ()
|
|
"Downcase the entry type and fields."
|
|
(interactive)
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((entry (bibtex-parse-entry))
|
|
(entry-fields)
|
|
(type (downcase (cdr (assoc "=type=" entry))))
|
|
(key (cdr (assoc "=key=" entry))))
|
|
|
|
(setq entry-fields (mapcar (lambda (x) (car x)) entry))
|
|
;; we do not want to reenter these fields
|
|
(setq entry-fields (remove "=key=" entry-fields))
|
|
(setq entry-fields (remove "=type=" entry-fields))
|
|
|
|
(bibtex-kill-entry)
|
|
(insert
|
|
(concat "@" (downcase type) "{" key ",\n"
|
|
(mapconcat
|
|
(lambda (field)
|
|
(format "%s = %s,"
|
|
(downcase field)
|
|
(cdr (assoc field entry))))
|
|
entry-fields "\n")
|
|
"\n}\n\n"))
|
|
(bibtex-find-entry key)
|
|
(bibtex-fill-entry)
|
|
(bibtex-clean-entry)))
|
|
|
|
|
|
;;** Clean a bibtex entry
|
|
;; These functions operate on a bibtex entry and "clean" it in some way.
|
|
|
|
(defun orcb-clean-nil (arg)
|
|
"Remove nil from some article fields.
|
|
The removal is conditional. Sometimes it is useful to have nil
|
|
around, e.g. for ASAP articles where the fields are not defined
|
|
yet but will be in the future.
|
|
|
|
With \\[univeral-argument], run `bibtex-clean-entry' after.
|
|
"
|
|
(interactive "P")
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((entry (bibtex-parse-entry))
|
|
(type (downcase (cdr (assoc "=type=" entry)))))
|
|
(when (string= type "article")
|
|
(cond
|
|
;; we have volume and pages but number is nil.
|
|
;; remove the number field.
|
|
((and (string= type "article")
|
|
(not (string= (cdr (assoc "volume" entry)) "{nil}"))
|
|
(not (string= (cdr (assoc "pages" entry)) "{nil}"))
|
|
(string= (cdr (assoc "number" entry)) "{nil}"))
|
|
(bibtex-set-field "number" "")
|
|
(if arg
|
|
(bibtex-clean-entry)))))))
|
|
|
|
|
|
(defun orcb-clean-nil-opinionated ()
|
|
"Remove nil from all article fields.
|
|
|
|
Note that by default, this will leave the entry empty, which may
|
|
then get deleted by `bibtex-clean-entry.' To disable this
|
|
behavior, remove opts-or-alts from `bibtex-entry-format'. This
|
|
will leave the empty entries so that you may fill them in later."
|
|
(interactive)
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((entry (bibtex-parse-entry))
|
|
(type (downcase (cdr (assoc "=type=" entry)))))
|
|
(when (string= type "article")
|
|
(cl-loop for (field . text) in entry do
|
|
(if (string= text "{nil}")
|
|
(bibtex-set-field field ""))))))
|
|
|
|
|
|
(defun orcb-clean-doi ()
|
|
"Remove http://dx.doi.org/ in the doi field."
|
|
(let ((doi (bibtex-autokey-get-field "doi")))
|
|
(when (string-match "^http://dx.doi.org/" doi)
|
|
(bibtex-beginning-of-entry)
|
|
(goto-char (car (cdr (bibtex-search-forward-field "doi" t))))
|
|
(bibtex-kill-field)
|
|
(bibtex-make-field "doi")
|
|
(backward-char)
|
|
(insert (replace-regexp-in-string "^http://dx.doi.org/" "" doi)))))
|
|
|
|
|
|
(defun orcb-clean-year (&optional new-year)
|
|
"Fix years set to 0.
|
|
If optional NEW-YEAR set it to that, otherwise prompt for it."
|
|
;; asap articles often set year to 0, which messes up key
|
|
;; generation. fix that.
|
|
(let ((year (bibtex-autokey-get-field "year")))
|
|
(when (string= "0" year)
|
|
(bibtex-beginning-of-entry)
|
|
(goto-char (car (cdr (bibtex-search-forward-field "year" t))))
|
|
(bibtex-kill-field)
|
|
(bibtex-make-field "year")
|
|
(backward-char)
|
|
(insert (or new-year (read-string "Enter year: "))))))
|
|
|
|
|
|
(defun orcb-clean-pages ()
|
|
"Check for empty pages, and put eid in its place if it exists."
|
|
(let ((pages (bibtex-autokey-get-field "pages"))
|
|
(eid (bibtex-autokey-get-field "eid")))
|
|
(when (and (not (string= "" eid))
|
|
(or (string= "" pages)))
|
|
(bibtex-set-field "pages" eid))))
|
|
|
|
|
|
(defun orcb-& ()
|
|
"Replace naked & with \& in a bibtex entry."
|
|
(save-restriction
|
|
(bibtex-narrow-to-entry)
|
|
(bibtex-beginning-of-entry)
|
|
(while (re-search-forward " & " nil t)
|
|
(replace-match " \\\\& "))))
|
|
|
|
|
|
(defun orcb-% ()
|
|
"Replace naked % with % in a bibtex entry."
|
|
(save-restriction
|
|
(bibtex-narrow-to-entry)
|
|
(bibtex-beginning-of-entry)
|
|
(while (re-search-forward "%" nil t)
|
|
(replace-match " \\\\%"))))
|
|
|
|
|
|
(defun orcb-key-comma ()
|
|
"Make sure there is a comma at the end of the first line."
|
|
(bibtex-beginning-of-entry)
|
|
(end-of-line)
|
|
;; some entries do not have a key or comma in first line. We check and add it,
|
|
;; if needed.
|
|
(unless (string-match ", *$" (thing-at-point 'line))
|
|
(end-of-line)
|
|
(insert ",")))
|
|
|
|
|
|
(defun orcb-key (&optional allow-duplicate-keys)
|
|
"Replace the key in the entry.
|
|
Prompts for replacement if the new key duplicates one already in
|
|
the file, unless ALLOW-DUPLICATE-KEYS is non-nil."
|
|
(let ((key (funcall org-ref-clean-bibtex-key-function
|
|
(bibtex-generate-autokey))))
|
|
;; remove any \\ in the key
|
|
(setq key (replace-regexp-in-string "\\\\" "" key))
|
|
;; first we delete the existing key
|
|
(bibtex-beginning-of-entry)
|
|
(re-search-forward 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)))
|
|
;; check if the key is in the buffer
|
|
(when (and (not allow-duplicate-keys)
|
|
(save-excursion
|
|
(bibtex-search-entry key)))
|
|
(save-excursion
|
|
(bibtex-search-entry key)
|
|
(bibtex-copy-entry-as-kill)
|
|
(switch-to-buffer-other-window "*duplicate entry*")
|
|
(bibtex-yank))
|
|
(setq key (bibtex-read-key "Duplicate Key found, edit: " key)))
|
|
|
|
(insert key)
|
|
(kill-new key)))
|
|
|
|
|
|
(defun orcb-check-journal ()
|
|
"Check entry at point to see if journal exists in `org-ref-bibtex-journal-abbreviations'.
|
|
If not, issue a warning."
|
|
(interactive)
|
|
(when
|
|
(string= "article"
|
|
(downcase
|
|
(cdr (assoc "=type=" (bibtex-parse-entry)))))
|
|
(save-excursion
|
|
(bibtex-beginning-of-entry)
|
|
(let* ((entry (bibtex-parse-entry t))
|
|
(journal (reftex-get-bib-field "journal" entry)))
|
|
(when (null journal)
|
|
(error "Unable to get journal for this entry."))
|
|
(unless (member journal (-flatten org-ref-bibtex-journal-abbreviations))
|
|
(message "Journal \"%s\" not found in org-ref-bibtex-journal-abbreviations." journal))))))
|
|
|
|
|
|
(defun orcb-fix-spacing ()
|
|
"Delete whitespace and fix spacing between entries."
|
|
(let (beg end)
|
|
(save-excursion
|
|
(save-restriction
|
|
(widen)
|
|
(bibtex-beginning-of-entry)
|
|
(setq beg (point))
|
|
(bibtex-end-of-entry)
|
|
(setq end (if (re-search-forward bibtex-any-entry-maybe-empty-head nil t)
|
|
(progn (beginning-of-line)
|
|
(point))
|
|
(point-max)))
|
|
;; 1. delete whitespace
|
|
(narrow-to-region beg end)
|
|
(delete-trailing-whitespace)
|
|
;; 2. delete consecutive empty lines
|
|
(goto-char end)
|
|
(while (re-search-backward "\n\n\n+" nil 'move)
|
|
(replace-match "\n\n"))
|
|
;; 3. add one line between entries
|
|
(goto-char end)
|
|
(forward-line -1)
|
|
(when (looking-at "[}][ \t]*\\|@Comment.+\\|%.+")
|
|
(end-of-line)
|
|
(newline))))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-clean-bibtex-entry ()
|
|
"Clean and replace the key in a bibtex entry.
|
|
See functions in `org-ref-clean-bibtex-entry-hook'."
|
|
(interactive)
|
|
(save-excursion
|
|
(save-restriction
|
|
(bibtex-narrow-to-entry)
|
|
(bibtex-beginning-of-entry)
|
|
;; run hooks. each of these operates on the entry with no arguments.
|
|
;; this did not work like i thought, it gives a symbolp error.
|
|
;; (run-hooks org-ref-clean-bibtex-entry-hook)
|
|
(mapc (lambda (x)
|
|
(save-restriction
|
|
(save-excursion
|
|
(funcall x))))
|
|
org-ref-clean-bibtex-entry-hook))))
|
|
|
|
(defun org-ref-get-citation-year (key)
|
|
"Get the year of an entry with KEY. Return year as a string."
|
|
(let* ((results (org-ref-get-bibtex-key-and-file key))
|
|
(bibfile (cdr results)))
|
|
(with-temp-buffer
|
|
(insert-file-contents bibfile)
|
|
(bibtex-set-dialect (parsebib-find-bibtex-dialect) t)
|
|
(bibtex-search-entry key nil 0)
|
|
(prog1 (reftex-get-bib-field "year" (bibtex-parse-entry t))))))
|
|
|
|
;;** Sort cite in cite link
|
|
;;;###autoload
|
|
(defun org-ref-sort-citation-link ()
|
|
"Replace link at point with sorted link by year."
|
|
(interactive)
|
|
(let* ((object (org-element-context))
|
|
(type (org-element-property :type object))
|
|
(begin (org-element-property :begin object))
|
|
(end (org-element-property :end object))
|
|
(link-string (org-element-property :path object))
|
|
keys years data)
|
|
(setq keys (org-ref-split-and-strip-string link-string))
|
|
(setq years (mapcar 'org-ref-get-citation-year keys))
|
|
(setq data (-zip-with 'cons years keys))
|
|
(setq data (cl-sort data (lambda (x y)
|
|
(< (string-to-number (car x))
|
|
(string-to-number (car y))))))
|
|
;; now get the keys separated by commas
|
|
(setq keys (mapconcat (lambda (x) (cdr x)) data ","))
|
|
(save-excursion
|
|
(goto-char begin)
|
|
(re-search-forward link-string)
|
|
(replace-match keys))))
|
|
|
|
|
|
;;** Shift-arrow sorting of keys in a cite link
|
|
(defun org-ref-swap-keys (i j keys)
|
|
"Swap index I and J in the list KEYS."
|
|
(let ((tempi (nth i keys)))
|
|
(setf (nth i keys) (nth j keys))
|
|
(setf (nth j keys) tempi))
|
|
keys)
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-swap-citation-link (direction)
|
|
"Move citation at point in DIRECTION +1 is to the right, -1 to the left."
|
|
(interactive)
|
|
(let* ((object (org-element-context))
|
|
(type (org-element-property :type object))
|
|
(begin (org-element-property :begin object))
|
|
(end (org-element-property :end object))
|
|
(link-string (org-element-property :path object))
|
|
key keys i)
|
|
;; We only want this to work on citation links
|
|
(when (-contains? org-ref-cite-types type)
|
|
(setq key (org-ref-get-bibtex-key-under-cursor))
|
|
(setq keys (org-ref-split-and-strip-string link-string))
|
|
(setq i (org-ref-list-index key keys)) ;; defined in org-ref
|
|
(if (> direction 0) ;; shift right
|
|
(org-ref-swap-keys i (+ i 1) keys)
|
|
(org-ref-swap-keys i (- i 1) keys))
|
|
(setq keys (mapconcat 'identity keys ","))
|
|
;; and replace the link with the sorted keys
|
|
(save-excursion
|
|
(goto-char begin)
|
|
(re-search-forward link-string)
|
|
(replace-match keys))
|
|
;; now go forward to key so we can move with the key
|
|
(re-search-forward key)
|
|
(goto-char (match-beginning 0)))))
|
|
|
|
|
|
;;** C-arrow navigation of cite keys
|
|
(defun org-ref-parse-cite ()
|
|
"Parse link to get cite keys, and start and end of the keys."
|
|
(interactive)
|
|
(let ((link (org-element-context))
|
|
path begin end
|
|
keys)
|
|
|
|
(unless (-contains? org-ref-cite-types
|
|
(org-element-property :type link))
|
|
(error "Not on a cite link"))
|
|
(setq path (org-element-property :path link)
|
|
begin (org-element-property :begin link)
|
|
end (org-element-property :end link))
|
|
|
|
(setq keys (org-ref-split-and-strip-string path))
|
|
(save-excursion
|
|
(cl-loop for key in keys
|
|
do
|
|
(goto-char begin)
|
|
(re-search-forward key end)
|
|
collect
|
|
(list key (match-beginning 0) (match-end 0))))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-next-key ()
|
|
"Move cursor to the next cite key when on a cite link.
|
|
Otherwise run `right-word'. If the cursor moves off the link,
|
|
move to the beginning of the next cite link after this one."
|
|
(interactive)
|
|
(let ((cps (org-ref-parse-cite))
|
|
(p (point)))
|
|
(cond
|
|
;; point is before first key
|
|
((< (point) (nth 1 (car cps)))
|
|
(goto-char (nth 1 (car cps))))
|
|
;; point is on a single key, or on the last key
|
|
((or (= 1 (length cps))
|
|
(> p (nth 1 (car (last cps)))))
|
|
(re-search-forward org-ref-cite-re nil t)
|
|
(goto-char (match-end 1))
|
|
(forward-char 1))
|
|
;; in a link with multiple keys. We need to figure out if there is a
|
|
;; next key and go to beginning
|
|
(t
|
|
(goto-char (min
|
|
(point-max)
|
|
(+ 1
|
|
(cl-loop for (k s e) in cps
|
|
if (and (>= p s)
|
|
(<= p e))
|
|
return e))))))
|
|
;; if we get off a link,jump to the next one.
|
|
(when
|
|
(not (-contains? org-ref-cite-types
|
|
(org-element-property
|
|
:type
|
|
(org-element-context))))
|
|
(when (re-search-forward org-ref-cite-re nil t)
|
|
(goto-char (match-beginning 0))
|
|
(re-search-forward ":")))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-previous-key ()
|
|
"Move cursor to the previous cite key when on a cite link.
|
|
Otherwise run `left-word'. If the cursor moves off the link,
|
|
move to the beginning of the previous cite link after this one."
|
|
(interactive)
|
|
(let ((cps (org-ref-parse-cite))
|
|
(p (point))
|
|
index)
|
|
(cond
|
|
;; point is on or before first key, go to previous link.
|
|
((<= (point) (nth 1 (car cps)))
|
|
(unless (re-search-backward org-ref-cite-re nil t)
|
|
(left-word))
|
|
(when (re-search-backward org-ref-cite-re nil t)
|
|
(goto-char (match-end 0))
|
|
(re-search-backward ",\\|:")
|
|
(forward-char)))
|
|
;; point is less than end of first key, goto beginning
|
|
((< p (nth 2 (car cps)))
|
|
;; we do this twice. the first one just goes to the beginning of the
|
|
;; current link
|
|
(goto-char (nth 1 (car cps))))
|
|
;; in a link with multiple keys. We need to figure out if there is a
|
|
;; previous key and go to beginning
|
|
(t
|
|
(setq index (cl-loop
|
|
for i from 0
|
|
for (k s e) in cps
|
|
if (and (>= p s)
|
|
(<= p e))
|
|
return i))
|
|
(goto-char (nth 1 (nth (- index 1) cps)))))))
|
|
|
|
|
|
;;** context around org-ref links
|
|
(defun org-ref-get-label-context (label)
|
|
"Return a string of context around a LABEL."
|
|
(save-excursion
|
|
(save-restriction
|
|
(widen)
|
|
(catch 'result
|
|
(goto-char (point-min))
|
|
(when (re-search-forward
|
|
(format "label:%s\\b" label) nil t)
|
|
(throw 'result (buffer-substring
|
|
(progn
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(point))
|
|
(progn
|
|
(forward-line 4)
|
|
(point)))))
|
|
|
|
(goto-char (point-min))
|
|
(when (re-search-forward
|
|
(format "\\label{%s}" label) nil t)
|
|
(throw 'result (buffer-substring
|
|
(progn
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(point))
|
|
(progn
|
|
(forward-line 4)
|
|
(point)))))
|
|
|
|
(goto-char (point-min))
|
|
(when (re-search-forward
|
|
(format "^\\( \\)*#\\+label:\\s-*\\(%s\\)\\b" label) nil t)
|
|
(throw 'result (buffer-substring
|
|
(progn
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(point))
|
|
(progn
|
|
(forward-line 4)
|
|
(point)))))
|
|
|
|
(goto-char (point-min))
|
|
(when (re-search-forward
|
|
(format "^\\( \\)*#\\+tblname:\\s-*\\(%s\\)\\b" label) nil t)
|
|
(throw 'result (buffer-substring
|
|
(progn
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(point))
|
|
(progn
|
|
(forward-line 4)
|
|
(point)))))
|
|
|
|
(goto-char (point-min))
|
|
(when (re-search-forward
|
|
(format "^\\( \\)*#\\+name:\\s-*\\(%s\\)\\b" label) nil t)
|
|
(throw 'result (buffer-substring
|
|
(progn
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(point))
|
|
(progn
|
|
(forward-line 4)
|
|
(point)))))
|
|
;; ;; CUSTOM_ID
|
|
(goto-char (point-min))
|
|
;; do we have a CUSTOM-ID?
|
|
(let ((heading (org-map-entries
|
|
(lambda ()
|
|
(buffer-substring
|
|
(progn
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(point))
|
|
(progn
|
|
(forward-line 4)
|
|
(point))))
|
|
(format "CUSTOM_ID=\"%s\"" label))))
|
|
;; (message-box heading)
|
|
(when heading
|
|
(throw 'result (car heading))))
|
|
;; radio target
|
|
(goto-char (point-min))
|
|
(when (re-search-forward (format "<<%s>>" (regexp-quote label)) nil t)
|
|
(throw 'result (match-string 0)))
|
|
|
|
|
|
(throw 'result "!!! NO CONTEXT FOUND !!!")))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-link-message ()
|
|
"Print a minibuffer message about the link that point is on."
|
|
(interactive)
|
|
;; the way links are recognized in org-element-context counts blank spaces
|
|
;; after a link and the closing brackets in literal links. We don't try to get
|
|
;; a message if the cursor is on those, or if it is on a blank line.
|
|
(when (not (or (looking-at " ") ;looking at a space
|
|
(looking-at "^$") ;looking at a blank line
|
|
(looking-at "]") ;looking at a bracket at the end
|
|
;looking at the end of the line.
|
|
(looking-at "$")))
|
|
|
|
(save-restriction
|
|
(widen)
|
|
(when (eq major-mode 'org-mode)
|
|
(let* ((object (org-element-context))
|
|
(type (org-element-property :type object)))
|
|
(save-excursion
|
|
(cond
|
|
;; cite links
|
|
((-contains? org-ref-cite-types type)
|
|
(message (org-ref-format-entry (org-ref-get-bibtex-key-under-cursor))))
|
|
|
|
;; message some context about the label we are referring to
|
|
((or (string= type "ref")
|
|
(string= type "cref")
|
|
(string= type "eqref")
|
|
(string= type "pageref")
|
|
(string= type "nameref")
|
|
(string= type "autoref"))
|
|
(if
|
|
(= (org-ref-count-labels
|
|
(org-element-property :path object))
|
|
0)
|
|
(message "!!! NO CONTEXT FOUND !!!count: 0")
|
|
(message "%scount: %s"
|
|
(org-ref-get-label-context
|
|
(org-element-property :path object))
|
|
(org-ref-count-labels
|
|
(org-element-property :path object)))))
|
|
|
|
;; message the count
|
|
((string= type "label")
|
|
(let ((count (org-ref-count-labels
|
|
(org-element-property :path object))))
|
|
;; get plurality on occurrence correct
|
|
(message (concat
|
|
(number-to-string count)
|
|
" occurrence"
|
|
(when (or (= count 0)
|
|
(> count 1))
|
|
"s")))))
|
|
|
|
((string= type "custom-id")
|
|
(save-excursion
|
|
(org-open-link-from-string
|
|
(format "[[#%s]]" (org-element-property :path object)))
|
|
(message "%s" (org-get-heading))))
|
|
|
|
;; check if the bibliography files exist.
|
|
((string= type "bibliography")
|
|
(let* ((bibfile)
|
|
;; object is the link you clicked on
|
|
(object (org-element-context))
|
|
(link-string (org-element-property :path object))
|
|
(link-string-beginning)
|
|
(link-string-end))
|
|
(save-excursion
|
|
(goto-char (org-element-property :begin object))
|
|
(search-forward link-string nil nil 1)
|
|
(setq link-string-beginning (match-beginning 0))
|
|
(setq link-string-end (match-end 0)))
|
|
|
|
;; make sure we are in link and not before the :
|
|
(when (> link-string-beginning (point))
|
|
(goto-char link-string-beginning))
|
|
|
|
(let (key-beginning key-end)
|
|
;; now if we have comma separated bibliographies
|
|
;; we find the one clicked on. we want to
|
|
;; search forward to next comma from point
|
|
(save-excursion
|
|
(if (search-forward "," link-string-end 1 1)
|
|
(setq key-end (- (match-end 0) 1)) ; we found a match
|
|
(setq key-end (point)))) ; no comma found so take the point
|
|
|
|
;; and backward to previous comma from point
|
|
(save-excursion
|
|
(if (search-backward "," link-string-beginning 1 1)
|
|
(setq key-beginning (+ (match-beginning 0) 1)) ; we found a match
|
|
(setq key-beginning (point)))) ; no match found
|
|
;; save the key we clicked on.
|
|
(setq bibfile
|
|
(org-ref-strip-string
|
|
(buffer-substring key-beginning key-end)))
|
|
(let ((file (org-ref-find-bibfile bibfile)))
|
|
(message (if file "%s exists." "!!! %s NOT FOUND !!!")
|
|
(or file bibfile)))))))))))))
|
|
|
|
;;** aliases
|
|
(defalias 'oro 'org-ref-open-citation-at-point)
|
|
(defalias 'orc 'org-ref-citation-at-point)
|
|
(defalias 'orp 'org-ref-open-pdf-at-point)
|
|
(defalias 'oru 'org-ref-open-url-at-point)
|
|
(defalias 'orn 'org-ref-open-notes-at-point)
|
|
|
|
|
|
(defalias 'orib 'org-ref-insert-bibliography-link)
|
|
(defalias 'oric 'org-ref-insert-cite-link)
|
|
(defalias 'orir 'org-ref-insert-ref-link)
|
|
(defalias 'orsl 'org-ref-store-bibtex-entry-link)
|
|
|
|
(defalias 'orcb 'org-ref-clean-bibtex-entry)
|
|
|
|
(defun org-ref-delete-cite-at-point ()
|
|
"Delete the citation link at point."
|
|
(let* ((cite (org-element-context))
|
|
(type (org-element-property :type cite)))
|
|
(when (-contains? org-ref-cite-types type)
|
|
(cl--set-buffer-substring
|
|
(org-element-property :begin cite)
|
|
(org-element-property :end cite)
|
|
""))))
|
|
|
|
|
|
(defun org-ref-update-pre-post-text ()
|
|
"Prompt for pre/post text and update link accordingly.
|
|
A blank string deletes pre/post text."
|
|
(save-excursion
|
|
(let* ((cite (org-element-context))
|
|
(type (org-element-property :type cite))
|
|
(key (org-element-property :path cite))
|
|
(text (read-from-minibuffer "Pre/post text: ")))
|
|
;; First we delete the citation
|
|
(when (-contains? org-ref-cite-types type)
|
|
(cl--set-buffer-substring
|
|
(org-element-property :begin cite)
|
|
(org-element-property :end cite)
|
|
""))
|
|
;; Then we reformat the citation
|
|
(if (string= text "")
|
|
(progn
|
|
(insert (format "%s:%s " type key))
|
|
;; Avoid space before punctuation
|
|
(when (looking-at "[[:punct:]]")
|
|
(delete-char 1)))
|
|
(insert (format "[[%s:%s][%s]] " type key text))
|
|
(when (looking-at "[[:punct:]]")
|
|
(delete-char 1))))))
|
|
|
|
|
|
(defun org-ref-delete-key-at-point ()
|
|
"Delete the key at point."
|
|
(save-excursion
|
|
(let* ((cite (org-element-context))
|
|
(path (org-element-property :path cite))
|
|
(keys (org-ref-split-and-strip-string path))
|
|
(key (org-ref-get-bibtex-key-under-cursor))
|
|
(begin (org-element-property :begin cite))
|
|
(end (org-element-property :end cite))
|
|
(type (org-element-property :type cite))
|
|
(bracketp (string= "[[" (buffer-substring begin (+ 2 begin))))
|
|
(trailing-space (if (save-excursion
|
|
(goto-char end)
|
|
(string= (string (preceding-char)) " "))
|
|
" " "")))
|
|
|
|
(setq keys (-remove-item key keys))
|
|
(setf (buffer-substring begin end)
|
|
(concat
|
|
(when bracketp "[[")
|
|
type ":" (mapconcat 'identity keys ",")
|
|
(when bracketp "]]")
|
|
trailing-space))
|
|
(kill-new key))))
|
|
|
|
|
|
(defun org-ref-insert-key-at-point (keys)
|
|
"Insert KEYS at point.
|
|
KEYS is a list of bibtex keys. If point is at : or earlier,
|
|
insert at the beginning. Otherwise, insert after the key at
|
|
point. Leaves point at end of added keys."
|
|
(interactive
|
|
(list
|
|
(funcall org-ref-cite-completion-function)))
|
|
(let* ((cite (org-element-context))
|
|
(type (org-element-property :type cite))
|
|
(p (point))
|
|
begin end
|
|
opath
|
|
okey okeys
|
|
ikey
|
|
bracket-p
|
|
trailing-space
|
|
newkeys
|
|
new-cite)
|
|
|
|
(cond
|
|
;; on a link, and before the keys. Insert keys at the beginning.
|
|
((and (-contains? org-ref-cite-types type)
|
|
(< (point) (+ (org-element-property :begin cite)
|
|
(length type) 1)))
|
|
(setq
|
|
begin (org-element-property :begin cite)
|
|
end (org-element-property :end cite)
|
|
opath (org-element-property :path cite)
|
|
okeys (org-ref-split-and-strip-string opath)
|
|
newkeys (append keys okeys)
|
|
bracket-p (string= "[" (buffer-substring begin (+ 1 begin)))
|
|
new-cite (concat
|
|
(when bracket-p "[[")
|
|
type
|
|
":"
|
|
(mapconcat 'identity newkeys ",")
|
|
(when bracket-p "]]")
|
|
trailing-space)))
|
|
|
|
;; on a link, stick new keys after current key
|
|
((or (-contains? org-ref-cite-types type)
|
|
(and (not (bobp))
|
|
(save-excursion
|
|
(forward-char -1)
|
|
(-contains?
|
|
org-ref-cite-types
|
|
(org-element-property :type (org-element-context))))))
|
|
|
|
;; we are after a cite. get back on it
|
|
(when (save-excursion
|
|
(forward-char -1)
|
|
(-contains?
|
|
org-ref-cite-types
|
|
(org-element-property :type (org-element-context))))
|
|
(forward-char -1))
|
|
|
|
(setq
|
|
cite (org-element-context)
|
|
type (org-element-property :type cite)
|
|
begin (org-element-property :begin cite)
|
|
end (org-element-property :end cite)
|
|
opath (org-element-property :path cite)
|
|
okeys (org-ref-split-and-strip-string opath)
|
|
okey (org-ref-get-bibtex-key-under-cursor)
|
|
ikey (org-ref-list-index okey okeys)
|
|
bracket-p (string= "[" (buffer-substring begin (+ 1 begin)))
|
|
trailing-space (if (save-excursion
|
|
(goto-char end)
|
|
(string= (string (preceding-char)) " "))
|
|
" " "")
|
|
newkeys (-flatten (-insert-at (+ 1 ikey) keys okeys))
|
|
new-cite (concat
|
|
(when bracket-p "[[")
|
|
type
|
|
":"
|
|
(mapconcat 'identity newkeys ",")
|
|
(when bracket-p "]]")
|
|
trailing-space)))
|
|
;; Looking back at a link beginning that a user has typed in
|
|
((save-excursion
|
|
(backward-word 1)
|
|
(looking-at (regexp-opt org-ref-cite-types)))
|
|
(setq begin (point)
|
|
end (point)
|
|
newkeys keys
|
|
new-cite (mapconcat 'identity keys ",")))
|
|
;; a new cite
|
|
(t
|
|
(setq
|
|
begin (point)
|
|
end (point)
|
|
type org-ref-default-citation-link
|
|
newkeys keys
|
|
bracket-p org-ref-prefer-bracket-links
|
|
new-cite (concat
|
|
(when bracket-p "[[")
|
|
type
|
|
":"
|
|
(mapconcat 'identity newkeys ",")
|
|
(when bracket-p "]]")
|
|
trailing-space))))
|
|
;; post link processing after all the variables habe been defined for each
|
|
;; case
|
|
(delete-region begin end)
|
|
(goto-char begin)
|
|
(insert new-cite)
|
|
(goto-char begin)
|
|
(re-search-forward (mapconcat 'identity keys ","))
|
|
(when (looking-at "]")
|
|
(forward-char 2))))
|
|
|
|
|
|
(defun org-ref-replace-key-at-point (&optional replacement-keys)
|
|
"Replace the key at point.
|
|
Optional REPLACEMENT-KEYS should be a string of comma-separated
|
|
keys. if it is not specified, find keys interactively."
|
|
(save-excursion
|
|
(let* ((cite (org-element-context))
|
|
(opath (org-element-property :path cite))
|
|
(okeys (org-ref-split-and-strip-string opath))
|
|
(okey (org-ref-get-bibtex-key-under-cursor))
|
|
(end (org-element-property :end cite)))
|
|
;; First, insert new keys at end
|
|
(save-excursion
|
|
(goto-char end)
|
|
(skip-chars-backward " ")
|
|
(if replacement-keys
|
|
(insert (format ",%s" replacement-keys))
|
|
(funcall org-ref-insert-cite-function)))
|
|
|
|
;; Now get the new keys, delete the old one and put the new ones in
|
|
(let* ((cite (org-element-context))
|
|
(type (org-element-property :type cite))
|
|
(path (org-element-property :path cite))
|
|
(keys (org-ref-split-and-strip-string path))
|
|
(new-keys (-difference keys okeys))
|
|
(key (org-ref-get-bibtex-key-under-cursor))
|
|
(begin (org-element-property :begin cite))
|
|
(end (org-element-property :end cite))
|
|
(bracketp (string= "[[" (buffer-substring begin (+ 2 begin))))
|
|
(trailing-space (if (save-excursion
|
|
(goto-char end)
|
|
(string= (string (preceding-char)) " "))
|
|
" " ""))
|
|
(index (org-ref-list-index key keys)))
|
|
;; keys here has the old key at index, and the new keys at the end.
|
|
;; delete old key
|
|
(setq keys (-remove-at index keys))
|
|
(dolist (nkey (reverse new-keys))
|
|
(setq keys (-insert-at index nkey keys)))
|
|
|
|
;; now remove off the end keys which are now duplicated.
|
|
(setq keys (nbutlast keys (length new-keys)))
|
|
|
|
(setf (buffer-substring begin end)
|
|
(concat
|
|
(when bracketp "[[")
|
|
type ":" (mapconcat 'identity keys ",")
|
|
(when bracketp "]]")
|
|
trailing-space))))))
|
|
|
|
|
|
(defun org-ref-insert-link (arg)
|
|
"Insert an org-ref link.
|
|
If no prefix ARG insert a cite.
|
|
If one prefix ARG insert a ref.
|
|
If two prefix ARGs insert a label.
|
|
|
|
This is a generic function. Specific completion engines might
|
|
provide their own version."
|
|
(interactive "P")
|
|
(cond
|
|
((eq arg nil)
|
|
(funcall org-ref-insert-cite-function))
|
|
((equal arg '(4))
|
|
(funcall org-ref-insert-ref-function))
|
|
((equal arg '(16))
|
|
(funcall org-ref-insert-label-function))))
|
|
|
|
;;* org-ref-help
|
|
;;;###autoload
|
|
(defun org-ref-help ()
|
|
"Open the `org-ref' manual."
|
|
(interactive)
|
|
(find-file (expand-file-name
|
|
"org-ref.org"
|
|
(file-name-directory
|
|
(find-library-name "org-ref")))))
|
|
|
|
|
|
;;* org-ref menu
|
|
|
|
(defun org-ref-org-menu ()
|
|
"Add `org-ref' menu to the Org menu."
|
|
|
|
(easy-menu-change
|
|
'("Org") "org-ref"
|
|
`(["Insert citation" ,org-ref-insert-cite-function]
|
|
["Insert ref" ,org-ref-insert-ref-function]
|
|
["Insert label" ,org-ref-insert-label-function]
|
|
"--"
|
|
["List of figures" org-ref-list-of-figures]
|
|
["List of tables" org-ref-list-of-tables]
|
|
["Extract bibtex entries" org-ref-extract-bibtex-entries]
|
|
["Check org-file" org-ref]
|
|
"--"
|
|
["Change completion backend" org-ref-change-completion]
|
|
"--"
|
|
["Help" org-ref-help]
|
|
["Customize org-ref" (customize-group 'org-ref)])
|
|
"Show/Hide")
|
|
|
|
(easy-menu-change '("Org") "--" nil "Show/Hide"))
|
|
|
|
(add-hook 'org-mode-hook 'org-ref-org-menu)
|
|
|
|
|
|
|
|
;;* The end
|
|
(provide 'org-ref-core)
|
|
|
|
;;; org-ref-core.el ends here
|