update packages
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@
|
||||
(defun org-ref-get-bibtex-key-under-cursor--display ()
|
||||
"Return key under the cursor in `org-mode'.
|
||||
If not on a key, but on a cite, prompt for key."
|
||||
(if-let ((key (get-text-property (point) 'cite-key)))
|
||||
(if-let* ((key (get-text-property (point) 'cite-key)))
|
||||
;; Point is on a key, so we get it directly
|
||||
key
|
||||
;; point is not on a key, but may still be on a cite link
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
;;; doi-utils.el --- DOI utilities for making bibtex entries
|
||||
;;; doi-utils.el --- DOI utilities for making bibtex entries -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2015-2021 John Kitchin
|
||||
|
||||
@@ -41,15 +41,14 @@
|
||||
(declare-function bibtex-completion-edit-notes "bibtex-completion")
|
||||
(declare-function org-bibtex-yank "org-bibtex")
|
||||
(declare-function org-ref-possible-bibfiles "org-ref-core")
|
||||
(declare-function org-ref-normalize-bibtex-completion-bibliography "org-ref-utils")
|
||||
|
||||
(declare-function f-ext? "f")
|
||||
(declare-function f-entries "f")
|
||||
(declare-function s-match "s")
|
||||
(declare-function org-ref--file-ext-p "org-ref-utils")
|
||||
(declare-function org-ref--directory-files "org-ref-utils")
|
||||
|
||||
(eval-when-compile
|
||||
(require 'cl-lib))
|
||||
(require 'bibtex)
|
||||
(require 'dash)
|
||||
(require 'json)
|
||||
(require 'org) ; org-add-link-type
|
||||
|
||||
@@ -59,7 +58,7 @@
|
||||
(require 'url-http)
|
||||
(require 'url-handlers)
|
||||
(require 'org-ref-utils)
|
||||
(require 'hydra)
|
||||
(require 'transient)
|
||||
|
||||
;;* Customization
|
||||
(defgroup doi-utils nil
|
||||
@@ -120,6 +119,35 @@ If nil use `doi-utils-get-bibtex-entry-pdf' synchronously."
|
||||
:group 'doi-utils)
|
||||
|
||||
|
||||
(defun doi-utils-pdf-filename-from-key ()
|
||||
"Generate PDF filename from bibtex entry key.
|
||||
This is the default function for `doi-utils-pdf-filename-function'.
|
||||
Returns the bibtex entry key as the filename (without extension or path)."
|
||||
(cdr (assoc "=key=" (bibtex-parse-entry))))
|
||||
|
||||
|
||||
(defcustom doi-utils-pdf-filename-function
|
||||
'doi-utils-pdf-filename-from-key
|
||||
"Function to generate PDF filename from bibtex entry.
|
||||
The function is called with no arguments while point is in the bibtex
|
||||
entry, and should return a string to use as the PDF filename (without
|
||||
the .pdf extension or directory path).
|
||||
|
||||
The directory path is determined separately by `bibtex-completion-library-path'.
|
||||
|
||||
The default function uses the bibtex entry key as the filename.
|
||||
|
||||
Example: To use the title field as the filename:
|
||||
(setq doi-utils-pdf-filename-function
|
||||
(lambda () (bibtex-autokey-get-field \"title\")))
|
||||
|
||||
Note: The function is responsible for ensuring the returned filename
|
||||
is valid for the filesystem. Special characters in fields like title
|
||||
may cause issues on some systems."
|
||||
:type 'function
|
||||
:group 'doi-utils)
|
||||
|
||||
|
||||
;;* Getting pdf files from a DOI
|
||||
|
||||
;; The idea here is simple. When you visit http://dx.doi.org/doi or
|
||||
@@ -308,7 +336,7 @@ https://onlinelibrary.wiley.com/doi/pdfdirect/10.1002/anie.201310461?download=tr
|
||||
(setq p2 (replace-regexp-in-string
|
||||
"^http\\(s?\\)://scitation.aip.org/" "" *doi-utils-redirect*))
|
||||
(setq s (split-string p2 "/"))
|
||||
(setq p1 (mapconcat 'identity (-remove-at-indices '(0 6) s) "/"))
|
||||
(setq p1 (mapconcat 'identity (org-ref--remove-at-indices '(0 6) s) "/"))
|
||||
(setq p3 (concat "/" (nth 0 s) (nth 1 s) "/" (nth 2 s) "/" (nth 3 s)))
|
||||
(format "http://scitation.aip.org/deliver/fulltext/%s.pdf?itemId=/%s&mimeType=pdf&containerItemId=%s"
|
||||
p1 p2 p3))))
|
||||
@@ -334,7 +362,7 @@ https://onlinelibrary.wiley.com/doi/pdfdirect/10.1002/anie.201310461?download=tr
|
||||
(defun ecs-pdf-url (*doi-utils-redirect*)
|
||||
"Get url to the pdf from *DOI-UTILS-REDIRECT*."
|
||||
(when (string-match "^http\\(s?\\)://jes.ecsdl.org" *doi-utils-redirect*)
|
||||
(replace-regexp-in-string "\.abstract$" ".full.pdf" *doi-utils-redirect*)))
|
||||
(replace-regexp-in-string "\\.abstract$" ".full.pdf" *doi-utils-redirect*)))
|
||||
|
||||
;; http://ecst.ecsdl.org/content/25/2/2769
|
||||
;; http://ecst.ecsdl.org/content/25/2/2769.full.pdf
|
||||
@@ -762,7 +790,7 @@ too. "
|
||||
(doi (replace-regexp-in-string
|
||||
"https?://\\(dx.\\)?.doi.org/" ""
|
||||
(bibtex-autokey-get-field "doi")))
|
||||
(key (cdr (assoc "=key=" (bibtex-parse-entry))))
|
||||
(base-name (funcall doi-utils-pdf-filename-function))
|
||||
(pdf-url)
|
||||
(pdf-file))
|
||||
|
||||
@@ -774,7 +802,7 @@ too. "
|
||||
(car bibtex-completion-library-path))
|
||||
(t
|
||||
(completing-read "Dir: " bibtex-completion-library-path)))
|
||||
key ".pdf"))
|
||||
base-name ".pdf"))
|
||||
|
||||
(unless doi (error "No DOI found to get a pdf for"))
|
||||
|
||||
@@ -834,7 +862,7 @@ checked."
|
||||
(doi (replace-regexp-in-string
|
||||
"https?://\\(dx.\\)?.doi.org/" ""
|
||||
(bibtex-autokey-get-field "doi")))
|
||||
(key (cdr (assoc "=key=" (bibtex-parse-entry))))
|
||||
(base-name (funcall doi-utils-pdf-filename-function))
|
||||
(pdf-url)
|
||||
(pdf-file))
|
||||
|
||||
@@ -846,7 +874,7 @@ checked."
|
||||
(car bibtex-completion-library-path))
|
||||
(t
|
||||
(completing-read "Dir: " bibtex-completion-library-path)))
|
||||
key ".pdf"))
|
||||
base-name ".pdf"))
|
||||
;; now get file if needed.
|
||||
(unless (file-exists-p pdf-file)
|
||||
(cond
|
||||
@@ -900,7 +928,7 @@ every field.")
|
||||
|
||||
(defun doi-utils-get-json-metadata (doi)
|
||||
"Try to get json metadata for DOI. Open the DOI in a browser if we do not get it."
|
||||
(if-let ((data (cdr (assoc doi doi-utils-cache))))
|
||||
(if-let* ((data (cdr (assoc doi doi-utils-cache))))
|
||||
;; We have the data already, so we return it.
|
||||
data
|
||||
(let ((url-request-method "GET")
|
||||
@@ -1055,7 +1083,7 @@ MATCHING-TYPES."
|
||||
fields)
|
||||
(concat
|
||||
,@(doi-utils-concat-prepare
|
||||
(-flatten
|
||||
(org-ref--flatten-list
|
||||
(list (concat "@" (symbol-name name) "{,\n")
|
||||
;; there seems to be some bug with mapcan,
|
||||
;; so we fall back to flatten
|
||||
@@ -1096,7 +1124,7 @@ MATCHING-TYPES."
|
||||
(let* ((results (funcall doi-utils-metadata-function doi))
|
||||
(type (plist-get results :type)))
|
||||
;; (format "%s" results) ; json-data
|
||||
(or (-some (lambda (g) (funcall g type results)) doi-utils-bibtex-type-generators)
|
||||
(or (cl-some (lambda (g) (funcall g type results)) doi-utils-bibtex-type-generators)
|
||||
(message "%s not supported yet\n%S." type results))))
|
||||
|
||||
;; That is just the string for the entry. To be useful, we need a function that
|
||||
@@ -1171,31 +1199,31 @@ Argument BIBFILE the bibliography to use."
|
||||
;; DOI raw
|
||||
;; Ex: 10.1109/MALWARE.2014.6999410
|
||||
((and (stringp the-active-region)
|
||||
(s-match (concat "^" doi-regexp) the-active-region))
|
||||
(org-ref--string-match (concat "^" doi-regexp) the-active-region))
|
||||
the-active-region)
|
||||
;; DOI url
|
||||
;; Ex: https://doi.org/10.1109/MALWARE.2014.6999410
|
||||
((and (stringp the-active-region)
|
||||
(s-match (concat doi-url-prefix-regexp doi-regexp) the-active-region))
|
||||
(org-ref--string-match (concat doi-url-prefix-regexp doi-regexp) the-active-region))
|
||||
(replace-regexp-in-string doi-url-prefix-regexp "" the-active-region))
|
||||
;; DOI url as customized
|
||||
((and (stringp the-active-region)
|
||||
(s-match (regexp-quote doi-utils-dx-doi-org-url) the-active-region))
|
||||
(org-ref--string-match (regexp-quote doi-utils-dx-doi-org-url) the-active-region))
|
||||
(replace-regexp-in-string (regexp-quote doi-utils-dx-doi-org-url) "" the-active-region))
|
||||
;; Check if DOI can be found in the current kill
|
||||
;; DOI raw
|
||||
;; Ex: 10.1109/MALWARE.2014.6999410
|
||||
((and (stringp the-current-kill)
|
||||
(s-match (concat "^" doi-regexp) the-current-kill))
|
||||
(org-ref--string-match (concat "^" doi-regexp) the-current-kill))
|
||||
the-current-kill)
|
||||
;; DOI url
|
||||
;; Ex: https://doi.org/10.1109/MALWARE.2014.6999410
|
||||
((and (stringp the-current-kill)
|
||||
(s-match (concat doi-url-prefix-regexp doi-regexp) the-current-kill))
|
||||
(org-ref--string-match (concat doi-url-prefix-regexp doi-regexp) the-current-kill))
|
||||
(replace-regexp-in-string doi-url-prefix-regexp "" the-current-kill))
|
||||
;; DOI url as customized
|
||||
((and (stringp the-current-kill)
|
||||
(s-match (regexp-quote doi-utils-dx-doi-org-url) the-current-kill))
|
||||
(org-ref--string-match (regexp-quote doi-utils-dx-doi-org-url) the-current-kill))
|
||||
(replace-regexp-in-string (regexp-quote doi-utils-dx-doi-org-url) "" the-current-kill))
|
||||
;; otherwise, return nil
|
||||
(t
|
||||
@@ -1251,7 +1279,7 @@ Optional argument NODELIM see `bibtex-make-field'."
|
||||
|
||||
(defun plist-get-keys (plist)
|
||||
"Return keys in a PLIST."
|
||||
(-slice plist 0 nil 2))
|
||||
(cl-loop for (key _value) on plist by #'cddr collect key))
|
||||
|
||||
|
||||
;;;###autoload
|
||||
@@ -1415,9 +1443,7 @@ May be empty if none are found."
|
||||
(defun doi-utils-open-bibtex (doi)
|
||||
"Search through variable `bibtex-completion-bibliography' for DOI."
|
||||
(interactive "sDOI: ")
|
||||
(cl-loop for f in (if (listp bibtex-completion-bibliography)
|
||||
bibtex-completion-bibliography
|
||||
(list bibtex-completion-bibliography))
|
||||
(cl-loop for f in (org-ref-normalize-bibtex-completion-bibliography)
|
||||
when (progn (find-file f)
|
||||
(when (search-forward doi (point-max) t)
|
||||
(bibtex-beginning-of-entry)))
|
||||
@@ -1453,23 +1479,40 @@ May be empty if none are found."
|
||||
|
||||
(declare-function org-element-property "org-element")
|
||||
|
||||
(defhydra doi-link-follow (:color blue :hint nil)
|
||||
"DOI actions:
|
||||
"
|
||||
("o" (doi-utils-open (org-element-property :path (org-element-context))) "open")
|
||||
("w" (doi-utils-wos (org-element-property :path (org-element-context))) "wos")
|
||||
("c" (doi-utils-wos-citing (org-element-property :path (org-element-context))) "wos citing articles")
|
||||
("r" (doi-utils-wos-related (org-element-property :path (org-element-context))) "wos related articles" )
|
||||
("a" (doi-utils-ads (org-element-property :path (org-element-context))) "ads")
|
||||
("s" (doi-utils-google-scholar (org-element-property :path (org-element-context))) "Google Scholar")
|
||||
("f" (doi-utils-crossref (org-element-property :path (org-element-context))) "CrossRef")
|
||||
("p" (doi-utils-pubmed (org-element-property :path (org-element-context))) "Pubmed")
|
||||
("b" (doi-utils-open-bibtex (org-element-property :path (org-element-context))) "open in bibtex")
|
||||
("g" (doi-utils-add-bibtex-entry-from-doi (org-element-property :path (org-element-context))) "get bibtex entry"))
|
||||
(defun doi-utils--context-doi ()
|
||||
(org-element-property :path (org-element-context)))
|
||||
|
||||
(transient-define-prefix doi-link-follow-menu ()
|
||||
"DOI actions."
|
||||
[["Actions"
|
||||
("o" "open" (lambda () (interactive)
|
||||
(doi-utils-open (doi-utils--context-doi))))
|
||||
("w" "wos" (lambda () (interactive)
|
||||
(doi-utils-wos (doi-utils--context-doi))))
|
||||
("c" "wos citing articles" (lambda () (interactive)
|
||||
(doi-utils-wos-citing (doi-utils--context-doi))))
|
||||
("r" "wos related articles" (lambda () (interactive)
|
||||
(doi-utils-wos-related (doi-utils--context-doi))))
|
||||
("a" "ads" (lambda () (interactive)
|
||||
(doi-utils-ads (doi-utils--context-doi))))
|
||||
("s" "Google Scholar" (lambda () (interactive)
|
||||
(doi-utils-google-scholar (doi-utils--context-doi))))
|
||||
("f" "CrossRef" (lambda () (interactive)
|
||||
(doi-utils-crossref (doi-utils--context-doi))))
|
||||
("p" "Pubmed" (lambda () (interactive)
|
||||
(doi-utils-pubmed (doi-utils--context-doi))))
|
||||
("b" "open in bibtex" (lambda () (interactive)
|
||||
(doi-utils-open-bibtex (doi-utils--context-doi))))
|
||||
("g" "get bibtex entry" (lambda () (interactive)
|
||||
(doi-utils-add-bibtex-entry-from-doi (doi-utils--context-doi))))
|
||||
("q" "quit" transient-quit-one)]])
|
||||
|
||||
(define-obsolete-function-alias 'doi-link-follow/body
|
||||
#'doi-link-follow-menu "3.1")
|
||||
|
||||
|
||||
(org-link-set-parameters "doi"
|
||||
:follow (lambda (_) (doi-link-follow/body))
|
||||
:follow (lambda (_) (doi-link-follow-menu))
|
||||
:export (lambda (doi desc format)
|
||||
(cond
|
||||
((eq format 'html)
|
||||
@@ -1639,7 +1682,7 @@ Get a list of possible matches. Choose one with completion."
|
||||
nil)))
|
||||
(completing-read
|
||||
"Bibfile: "
|
||||
(append (f-entries "." (lambda (f) (f-ext? f "bib")))
|
||||
(append (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib")))
|
||||
bibtex-completion-bibliography))))
|
||||
(let* ((json-data (with-temp-buffer
|
||||
(url-insert
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
;;; openalex.el --- Org-ref interface to OpenAlex
|
||||
;;; openalex.el --- Org-ref interface to OpenAlex -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; This is an elisp interface to OpenAlex (https://docs.openalex.org/) for org-ref.
|
||||
@@ -25,14 +25,13 @@
|
||||
;; `user-mail-address' value will be added to the queries if it exists so you
|
||||
;; will get the polite pool.
|
||||
;;
|
||||
;; This library extends the `org-ref-citation-hydra' and adds keys to get to
|
||||
;; This library extends the `org-ref-citation-menu' and adds keys to get to
|
||||
;; cited by, references and related documents in OpenAlex.
|
||||
|
||||
(require 'dash)
|
||||
(require 's)
|
||||
(require 'request)
|
||||
(require 'doi-utils)
|
||||
(require 'org-ref-citation-links)
|
||||
(require 'org-ref-utils)
|
||||
|
||||
(declare-function org-ref-possible-bibfiles "org-ref-core")
|
||||
(declare-function org-ref-find-bibliography "org-ref-core")
|
||||
@@ -40,6 +39,7 @@
|
||||
(declare-function bibtex-completion-show-entry "bibtex-completion")
|
||||
(declare-function bibtex-completion-apa-format-reference "bibtex-completion")
|
||||
(declare-function ivy-read "ivy")
|
||||
(declare-function ivy-more-chars "ivy")
|
||||
|
||||
(defcustom oa-api-key
|
||||
nil
|
||||
@@ -59,6 +59,22 @@
|
||||
(json-read)))
|
||||
|
||||
|
||||
(defun oa--params (&rest params)
|
||||
"Build API request parameters, excluding nil values.
|
||||
PARAMS should be an alist of (key . value) pairs.
|
||||
The mailto and api_key parameters are added automatically."
|
||||
(let ((result (when user-mail-address
|
||||
`(("mailto" . ,user-mail-address)))))
|
||||
;; Only add api_key if it's set and non-empty
|
||||
(when (and oa-api-key (not (string-empty-p oa-api-key)))
|
||||
(push `("api_key" . ,oa-api-key) result))
|
||||
;; Add other params, filtering out nil values
|
||||
(dolist (param params)
|
||||
(when (cdr param)
|
||||
(push param result)))
|
||||
(nreverse result)))
|
||||
|
||||
|
||||
(defun oa-get (data query &optional iterable)
|
||||
"Get fields from DATA with QUERY.
|
||||
QUERY is a dot notation string.
|
||||
@@ -77,7 +93,7 @@ Assumes data is in plist form."
|
||||
data)
|
||||
|
||||
;; query[] means get query then turn iteration on
|
||||
((and (s-ends-with-p "[]" current-field) (null iterable))
|
||||
((and (string-suffix-p "[]" current-field) (null iterable))
|
||||
(setq current-field (substring current-field 0 -2))
|
||||
(oa-get (plist-get data (intern-soft (concat ":" current-field)))
|
||||
(when fields
|
||||
@@ -86,7 +102,7 @@ Assumes data is in plist form."
|
||||
|
||||
;; this means another level of iteration. You already have a collection. we
|
||||
;; have to iterate over each one I think.
|
||||
((and (s-ends-with-p "[]" current-field) iterable)
|
||||
((and (string-suffix-p "[]" current-field) iterable)
|
||||
(setq current-field (substring current-field 0 -2))
|
||||
(cl-loop for item in data collect
|
||||
(oa-get (plist-get item (intern-soft (concat ":" current-field)))
|
||||
@@ -113,17 +129,16 @@ Assumes data is in plist form."
|
||||
(defun oa--query-all-data (endpoint &rest filter)
|
||||
(let* ((page 1)
|
||||
(url (concat "https://api.openalex.org/" endpoint))
|
||||
(filter-string (string-join (cl-loop for key in (plist-get-keys filter) collect
|
||||
(concat (substring (symbol-name key) 1)
|
||||
":"
|
||||
(url-hexify-string
|
||||
(plist-get filter key))))
|
||||
","))
|
||||
(params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("filter" . ,filter-string)
|
||||
("page" . ,page)))
|
||||
|
||||
(filter-string (string-join
|
||||
(cl-loop for key in (plist-get-keys filter) collect
|
||||
(concat (substring (symbol-name key) 1)
|
||||
":"
|
||||
(url-hexify-string
|
||||
(plist-get filter key))))
|
||||
","))
|
||||
(params (oa--params `("filter" . ,filter-string)
|
||||
`("page" . ,page)))
|
||||
|
||||
(req (request url :sync t :parser 'oa--response-parser :params params))
|
||||
(data (request-response-data req))
|
||||
(meta (plist-get data :meta))
|
||||
@@ -133,7 +148,7 @@ Assumes data is in plist form."
|
||||
(results (plist-get data :results)))
|
||||
(cl-loop for i from 2 to pages do
|
||||
(setf (cdr (assoc "page" params)) i)
|
||||
(setq req (request purl :sync t :parser 'oa--response-parser :params params)
|
||||
(setq req (request url :sync t :parser 'oa--response-parser :params params)
|
||||
data (request-response-data req)
|
||||
results (append results (plist-get data :results))))
|
||||
results))
|
||||
@@ -171,12 +186,10 @@ non-nil, and `oa-api-key' if it is non-nil to the API url."
|
||||
(url-hexify-string
|
||||
(plist-get filter key))))
|
||||
","))
|
||||
|
||||
|
||||
(req (request url :sync t :parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("filter" . ,filter-string)
|
||||
("page" . ,page))))
|
||||
:params (oa--params `("filter" . ,filter-string)
|
||||
`("page" . ,page))))
|
||||
(data (request-response-data req))
|
||||
(meta (plist-get data :meta))
|
||||
(count (plist-get meta :count))
|
||||
@@ -184,7 +197,7 @@ non-nil, and `oa-api-key' if it is non-nil to the API url."
|
||||
(pages (ceiling (/ (float count) per-page)))
|
||||
(results (plist-get data :results))
|
||||
(next-page (format "[[elisp:(oa-query \"%s\" %s :page \"%s\")][Next page: %s]]"
|
||||
endpoint
|
||||
endpoint
|
||||
(string-join (cl-loop for x in filter
|
||||
collect
|
||||
(if (keywordp x)
|
||||
@@ -194,7 +207,7 @@ non-nil, and `oa-api-key' if it is non-nil to the API url."
|
||||
(+ page 1)
|
||||
(+ page 1)))
|
||||
(buf (generate-new-buffer "*OpenAlex - Query*")))
|
||||
|
||||
|
||||
(with-current-buffer buf
|
||||
(erase-buffer)
|
||||
(org-mode)
|
||||
@@ -216,7 +229,7 @@ non-nil, and `oa-api-key' if it is non-nil to the API url."
|
||||
((string= endpoint "works")
|
||||
(string-join
|
||||
(cl-loop for wrk in results collect
|
||||
(s-format "*** ${title}
|
||||
(org-ref--format-template "*** ${title}
|
||||
:PROPERTIES:
|
||||
:HOST: ${primary_location.source.display_name}
|
||||
:YEAR: ${publication_year}
|
||||
@@ -237,8 +250,6 @@ ${get-bibtex}
|
||||
${abstract}
|
||||
|
||||
"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("title" . ,(oa--title wrk))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("publication_year" . ,(oa-get wrk "publication_year"))
|
||||
@@ -283,9 +294,7 @@ https://docs.openalex.org/api-entities/works"
|
||||
(t
|
||||
(format "/%s" entity-id)))))
|
||||
(req (request url :sync t :parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("filter" . ,filter))))
|
||||
:params (oa--params `("filter" . ,filter))))
|
||||
(data (request-response-data req)))
|
||||
;; this is for convenience to inspect data in a browser, e.g. you can click
|
||||
;; on the url in Emacs and it opens in a browser.
|
||||
@@ -301,9 +310,10 @@ https://docs.openalex.org/api-entities/works"
|
||||
(ivy-more-chars)
|
||||
(let* ((url "https://api.openalex.org/autocomplete/works")
|
||||
(req (request url :sync t :parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("q" . ,query))))
|
||||
:params (let ((params `(("q" . ,query))))
|
||||
(when user-mail-address
|
||||
(push `("mailto" . ,user-mail-address) params))
|
||||
(nreverse params))))
|
||||
(data (request-response-data req))
|
||||
(results (plist-get data :results)))
|
||||
(cl-loop for work in results collect
|
||||
@@ -318,11 +328,11 @@ https://docs.openalex.org/api-entities/works"
|
||||
"Autocomplete works.
|
||||
This doesn't seem as useful as it could be."
|
||||
(interactive)
|
||||
|
||||
|
||||
(ivy-read "Work: " #'oa--works-candidates
|
||||
:dynamic-collection t
|
||||
:action
|
||||
'(1
|
||||
'(1
|
||||
("o" (lambda (candidate)
|
||||
(browse-url (get-text-property 0 'oaid candidate)))
|
||||
"Open in browser"))))
|
||||
@@ -338,7 +348,7 @@ This doesn't seem as useful as it could be."
|
||||
(defun oa--authors (wrk)
|
||||
"Return an author string for WRK.
|
||||
The string is a comma-separated list of links to author pages in OpenAlex."
|
||||
(s-join ", " (cl-loop for author in (plist-get wrk :authorships)
|
||||
(string-join (cl-loop for author in (plist-get wrk :authorships)
|
||||
collect
|
||||
(format "[[elisp:(oa--author-org \"%s\")][%s]]"
|
||||
(plist-get
|
||||
@@ -346,7 +356,8 @@ The string is a comma-separated list of links to author pages in OpenAlex."
|
||||
:id)
|
||||
(plist-get
|
||||
(plist-get author :author)
|
||||
:display_name)))))
|
||||
:display_name)))
|
||||
", "))
|
||||
|
||||
|
||||
(defun oa--title (wrk)
|
||||
@@ -358,7 +369,7 @@ The string is a comma-separated list of links to author pages in OpenAlex."
|
||||
;; be nice to integrate M-, navigation.
|
||||
(defun oa--elisp-get-bibtex (wrk)
|
||||
"Return a elisp link to get a bibtex entry for WRK if there is a doi."
|
||||
(if-let ((doi (plist-get wrk :doi)))
|
||||
(if-let* ((doi (plist-get wrk :doi)))
|
||||
(format "[[elisp:(doi-add-bibtex-entry \"%s\")][Get bibtex entry]]" doi)
|
||||
""))
|
||||
|
||||
@@ -389,7 +400,7 @@ The string is a comma-separated list of links to author pages in OpenAlex."
|
||||
WORKS is a list of results from OpenAlex."
|
||||
(cl-loop for wrk in (plist-get works :results)
|
||||
collect
|
||||
(s-format "** ${title}
|
||||
(org-ref--format-template "** ${title}
|
||||
:PROPERTIES:
|
||||
:HOST: ${primary_location.source.display_name}
|
||||
:YEAR: ${publication_year}
|
||||
@@ -407,8 +418,6 @@ ${get-bibtex}
|
||||
- ${oa-cited}
|
||||
|
||||
"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("title" . ,(oa-get wrk "title"))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("publication_year" . ,(oa-get wrk "publication_year"))
|
||||
@@ -428,7 +437,7 @@ HEADER is the first thing in the buffer
|
||||
WORKS is usually a list of results from OpenAlex.
|
||||
Argument ENTRIES A list of strings for each org entry."
|
||||
(let ((buf (get-buffer-create bufname)))
|
||||
|
||||
|
||||
(with-current-buffer buf
|
||||
(erase-buffer)
|
||||
(insert header)
|
||||
@@ -441,7 +450,7 @@ elisp:org-columns elisp:org-columns-quit
|
||||
| cited by | [[elisp:(oa-buffer-sort-cited-by-count t)][low first]] | [[elisp:(oa-buffer-sort-cited-by-count)][high first]] |
|
||||
|
||||
")
|
||||
(insert (s-join "\n" entries))
|
||||
(insert (string-join entries "\n"))
|
||||
(org-mode)
|
||||
(goto-char (point-min))
|
||||
(org-next-visible-heading 1))
|
||||
@@ -456,21 +465,21 @@ elisp:org-columns elisp:org-columns-quit
|
||||
split
|
||||
entries)
|
||||
(while related-work
|
||||
(setq split (-split-at 25 related-work)
|
||||
(setq split (org-ref--split-at 25 related-work)
|
||||
related-work (nth 1 split))
|
||||
|
||||
|
||||
;; split is what we process now
|
||||
(setq entries (append entries
|
||||
(oa--works-entries
|
||||
(oa--work (format "?filter=openalex:%s" (s-join "|" (nth 0 split))))))))
|
||||
|
||||
(oa--work (format "?filter=openalex:%s" (string-join (nth 0 split) "|")))))))
|
||||
|
||||
(oa--works-buffer
|
||||
"*OpenAlex - Related works*"
|
||||
(format "* OpenAlex - Related works for %s ([[%s][json]])
|
||||
%s\n\n"
|
||||
entity-id
|
||||
(plist-get wrk :oa-url)
|
||||
(s-format ":PROPERTIES:
|
||||
(org-ref--format-template ":PROPERTIES:
|
||||
:TITLE: ${title}
|
||||
:HOST: ${primary_location.source.display_name}
|
||||
:AUTHOR: ${authors}
|
||||
@@ -481,15 +490,13 @@ elisp:org-columns elisp:org-columns-quit
|
||||
|
||||
Found ${nentries} results.
|
||||
"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("title" . ,(oa-get wrk "title"))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("authors" . ,(oa--authors wrk))
|
||||
("doi" . ,(oa-get wrk "doi"))
|
||||
("publication_year" . ,(oa-get wrk "publication_year"))
|
||||
("id" . ,(oa-get wrk "id"))
|
||||
("nentries" . ,(length entries)))))
|
||||
`(("title" . ,(oa-get wrk "title"))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("authors" . ,(oa--authors wrk))
|
||||
("doi" . ,(oa-get wrk "doi"))
|
||||
("publication_year" . ,(oa-get wrk "publication_year"))
|
||||
("id" . ,(oa-get wrk "id"))
|
||||
("nentries" . ,(length entries)))))
|
||||
entries)))
|
||||
|
||||
|
||||
@@ -500,20 +507,20 @@ Found ${nentries} results.
|
||||
split
|
||||
(entries '()))
|
||||
(while referenced-work
|
||||
(setq split (-split-at 25 referenced-work)
|
||||
(setq split (org-ref--split-at 25 referenced-work)
|
||||
referenced-work (nth 1 split))
|
||||
;; split is what we process now
|
||||
(setq entries (append entries
|
||||
(oa--works-entries
|
||||
(oa--work (format "?filter=openalex:%s"
|
||||
(s-join "|" (nth 0 split))))))))
|
||||
(oa--work (format "?filter=openalex:%s"
|
||||
(string-join (nth 0 split) "|")))))))
|
||||
(oa--works-buffer
|
||||
"*OpenAlex - References*"
|
||||
(format "* OpenAlex - References from %s ([[%s][json]])
|
||||
%s\n\n"
|
||||
entity-id
|
||||
(plist-get wrk :oa-url)
|
||||
(s-format ":PROPERTIES:
|
||||
(org-ref--format-template ":PROPERTIES:
|
||||
:TITLE: ${title}
|
||||
:HOST: ${primary_location.source.display_name}
|
||||
:AUTHOR: ${authors}
|
||||
@@ -524,15 +531,13 @@ Found ${nentries} results.
|
||||
|
||||
Found ${nentries} results.
|
||||
"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("title" . ,(oa-get wrk "title"))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("authors" . ,(oa--authors wrk))
|
||||
("doi" . ,(oa-get wrk "doi"))
|
||||
("publication_year" . ,(oa-get wrk "publication_year"))
|
||||
("id" . ,(oa-get wrk "id"))
|
||||
("nentries" . ,(length entries)))))
|
||||
`(("title" . ,(oa-get wrk "title"))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("authors" . ,(oa--authors wrk))
|
||||
("doi" . ,(oa-get wrk "doi"))
|
||||
("publication_year" . ,(oa-get wrk "publication_year"))
|
||||
("id" . ,(oa-get wrk "id"))
|
||||
("nentries" . ,(length entries)))))
|
||||
entries)))
|
||||
|
||||
|
||||
@@ -546,8 +551,7 @@ Found ${nentries} results.
|
||||
(request url
|
||||
:sync t
|
||||
:parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)))))
|
||||
:params (oa--params))))
|
||||
(count (plist-get (plist-get cited-by-works :meta) :count))
|
||||
(per-page (plist-get (plist-get cited-by-works :meta) :per_page))
|
||||
(entries '())
|
||||
@@ -559,19 +563,17 @@ Found ${nentries} results.
|
||||
(request url
|
||||
:sync t
|
||||
:parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("page" . ,page)))))
|
||||
:params (oa--params `("page" . ,page)))))
|
||||
(setq entries (append entries (oa--works-entries cited-by-works)))
|
||||
(cl-incf page))
|
||||
|
||||
|
||||
(oa--works-buffer
|
||||
"*OpenAlex - Cited by*"
|
||||
(format "* OpenAlex - %s Cited by ([[%s][json]])
|
||||
%s"
|
||||
entity-id
|
||||
url
|
||||
(s-format ":PROPERTIES:
|
||||
(org-ref--format-template ":PROPERTIES:
|
||||
:TITLE: ${title}
|
||||
:HOST: ${primary_location.source.display_name}
|
||||
:AUTHOR: ${authors}
|
||||
@@ -583,8 +585,6 @@ Found ${nentries} results.
|
||||
Found ${nentries} results.
|
||||
|
||||
"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("title" . ,(oa-get wrk "title"))
|
||||
("primary_location.source.display_name" . ,(oa-get wrk "primary_location.source.display_name"))
|
||||
("authors" . ,(oa--authors wrk))
|
||||
@@ -665,12 +665,15 @@ With prefix arg ASCENDING sort from low to high."
|
||||
(browse-url (plist-get data :id))))
|
||||
|
||||
|
||||
(defhydra+ org-ref-citation-hydra ()
|
||||
"Add open from action to `org-ref-citation-hydra'."
|
||||
("xa" oa-open "Open in OpenAlex" :column "OpenAlex")
|
||||
("xr" oa-related-works "Related documents" :column "OpenAlex")
|
||||
("xc" oa-cited-by-works "Cited by documents" :column "OpenAlex")
|
||||
("xf" oa-referenced-works "References from" :column "OpenAlex"))
|
||||
(with-eval-after-load 'org-ref-citation-links
|
||||
(transient-append-suffix 'org-ref-citation-menu "u"
|
||||
'("xa" "Open in OpenAlex" oa-open))
|
||||
(transient-append-suffix 'org-ref-citation-menu "xa"
|
||||
'("xr" "Related documents" oa-related-works))
|
||||
(transient-append-suffix 'org-ref-citation-menu "xr"
|
||||
'("xc" "Cited by documents" oa-cited-by-works))
|
||||
(transient-append-suffix 'org-ref-citation-menu "xc"
|
||||
'("xf" "References from" oa-referenced-works)))
|
||||
|
||||
|
||||
;; * Author object
|
||||
@@ -681,10 +684,8 @@ FILTER is an optional string to add to the URL."
|
||||
(let* ((url (concat "https://api.openalex.org/authors"
|
||||
entity-id))
|
||||
(req (request url :sync t :parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("filter" . ,filter))))
|
||||
(data (request-response-data req)))
|
||||
:params (oa--params `("filter" . ,filter))))
|
||||
(data (request-response-data req)))
|
||||
;; this is for convenience to inspect data in a browser.
|
||||
(plist-put data :oa-url url)
|
||||
data))
|
||||
@@ -692,15 +693,14 @@ FILTER is an optional string to add to the URL."
|
||||
|
||||
(defun oa--author-entries (works-data url)
|
||||
"Get entries from WORKS-DATA."
|
||||
(let* ((meta (plist-get works-data :meta))
|
||||
(let* ((meta (plist-get works-data :meta))
|
||||
(per-page (plist-get meta :per_page))
|
||||
(count (plist-get meta :count))
|
||||
(pages (/ count per-page))
|
||||
(entries '())
|
||||
purl)
|
||||
(entries '()))
|
||||
;; if there is a remainder we need to get the rest
|
||||
(when (> (mod count per-page) 0) (cl-incf pages))
|
||||
|
||||
|
||||
;; Now we have to loop through the pages
|
||||
(cl-loop for i from 1 to pages
|
||||
do
|
||||
@@ -708,13 +708,11 @@ FILTER is an optional string to add to the URL."
|
||||
(request url
|
||||
:sync t
|
||||
:parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("page" . ,i))))
|
||||
:params (oa--params `("page" . ,i))))
|
||||
entries (append entries
|
||||
(cl-loop for result in (plist-get works-data :results)
|
||||
collect
|
||||
(s-format "*** ${title}
|
||||
(org-ref--format-template "*** ${title}
|
||||
:PROPERTIES:
|
||||
:ID: ${id}
|
||||
:DOI: ${ids.doi}
|
||||
@@ -734,9 +732,7 @@ ${get-bibtex}
|
||||
|
||||
${abstract}
|
||||
|
||||
" (lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("title" . ,(oa--title result))
|
||||
" `(("title" . ,(oa--title result))
|
||||
("id" . ,(oa-get result "id"))
|
||||
("ids.doi" . ,(oa-get result "ids.doi"))
|
||||
("publication_year" . ,(oa-get result "publication_year"))
|
||||
@@ -787,9 +783,10 @@ ${abstract}
|
||||
(ivy-more-chars)
|
||||
(let* ((url "https://api.openalex.org/autocomplete/authors")
|
||||
(req (request url :sync t :parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("q" . ,query))))
|
||||
:params (let ((params `(("q" . ,query))))
|
||||
(when user-mail-address
|
||||
(push `("mailto" . ,user-mail-address) params))
|
||||
(nreverse params))))
|
||||
(data (request-response-data req))
|
||||
(results (plist-get data :results)))
|
||||
(cl-loop for author in results collect
|
||||
@@ -801,6 +798,20 @@ ${abstract}
|
||||
'oaid (plist-get author :id))))))
|
||||
|
||||
|
||||
(defun oa--format-institution (data)
|
||||
"Format institution from DATA, handling nil values gracefully.
|
||||
Extracts the first institution from last_known_institutions array."
|
||||
(let* ((institutions (plist-get data :last_known_institutions))
|
||||
(first-inst (and institutions (listp institutions) (car institutions)))
|
||||
(name (and first-inst (plist-get first-inst :display_name)))
|
||||
(country (and first-inst (plist-get first-inst :country_code))))
|
||||
(cond
|
||||
((and name country) (format "%s, %s" name country))
|
||||
(name name)
|
||||
(country country)
|
||||
(t ""))))
|
||||
|
||||
|
||||
(defun oa--counts-by-year (data)
|
||||
"Get citation counts by year and make a graph.
|
||||
DATA is an author from OpenAlex.
|
||||
@@ -858,18 +869,17 @@ plot $counts using 1:3:xtic(2) with boxes lc rgb \"grey\" title \"Citations per
|
||||
(request works-url
|
||||
:sync t
|
||||
:parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key))))))
|
||||
:params (oa--params)))))
|
||||
(with-current-buffer buf
|
||||
(erase-buffer)
|
||||
(insert (s-format "* ${display_name} ([[${oa-url}][json]])
|
||||
(insert (org-ref--format-template "* ${display_name} ([[${oa-url}][json]])
|
||||
:PROPERTIES:
|
||||
:OPENALEX: ${id}
|
||||
:ORCID: ${orcid}
|
||||
:SCOPUS: ${ids.scopus}
|
||||
:WORKS_COUNT: ${works_count}
|
||||
:CITED_BY_COUNT: ${cited_by_count}
|
||||
:INSTITUTION: ${last_known_institution.display_name}, ${last_known_institution.country_code}
|
||||
:INSTITUTION: ${institution}
|
||||
:END:
|
||||
|
||||
#+COLUMNS: %25ITEM %YEAR %CITED_BY_COUNT
|
||||
@@ -884,18 +894,16 @@ ${citations-image}
|
||||
| cited by | [[elisp:(oa-buffer-sort-cited-by-count t)][low first]] | [[elisp:(oa-buffer-sort-cited-by-count)][high first]] |
|
||||
|
||||
"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("display_name" . ,(oa-get data "display_name"))
|
||||
("oa-url" . ,(oa-get data "oa-url"))
|
||||
("id" . ,(oa-get data "id"))
|
||||
("orcid" . ,(oa-get data "orcid"))
|
||||
("ids.scopus" . ,(oa-get data "ids.scopus"))
|
||||
("works_count" . ,(oa-get data "works_count"))
|
||||
("last_known_institution.display_name" . ,(oa-get data "last_known_institution.display_name"))
|
||||
("last_known_institution.country_code" . ,(oa-get data "last_known_institution.country_code"))
|
||||
("cited_by_count" . ,(oa-get data "cited_by_count"))
|
||||
("institution" . ,(oa--format-institution data))
|
||||
("citations-image" . ,citations-image))))
|
||||
(insert (s-join "\n" (oa--author-entries works-data works-url)))
|
||||
(insert (string-join (oa--author-entries works-data works-url) "\n"))
|
||||
|
||||
(org-mode)
|
||||
(goto-char (point-min))
|
||||
@@ -906,7 +914,7 @@ ${citations-image}
|
||||
(defun oa-author ()
|
||||
"Get data and act on it for an author."
|
||||
(interactive)
|
||||
|
||||
|
||||
(ivy-read "Author: " #'oa--author-candidates
|
||||
:dynamic-collection t
|
||||
:action
|
||||
@@ -936,10 +944,8 @@ PAGE is optional, and loads that page of results. Defaults to 1."
|
||||
(req (request url
|
||||
:sync t
|
||||
:parser #'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("page" . ,page)
|
||||
("filter" . ,(format "fulltext.search:%s" query)))))
|
||||
:params (oa--params `("page" . ,page)
|
||||
`("filter" . ,(format "fulltext.search:%s" query)))))
|
||||
(data (request-response-data req))
|
||||
(metadata (plist-get data :meta))
|
||||
(count (plist-get metadata :count))
|
||||
@@ -952,29 +958,27 @@ PAGE is optional, and loads that page of results. Defaults to 1."
|
||||
(+ page 1)
|
||||
npages))
|
||||
(buf (get-buffer-create "*OpenAlex Full-text search*")))
|
||||
|
||||
|
||||
(with-current-buffer buf
|
||||
(erase-buffer)
|
||||
(org-mode)
|
||||
(insert (s-format "#+title: Full-text search: ${query}
|
||||
(insert (org-ref--format-template "#+title: Full-text search: ${query}
|
||||
|
||||
[[elisp:(oa-fulltext-search \"${query}\" ${page})]]"
|
||||
'aget
|
||||
`(("query" . ,query)
|
||||
("page" . ,page))))
|
||||
(insert (s-format
|
||||
(insert (org-ref--format-template
|
||||
"
|
||||
${meta.count} results: Page ${meta.page} of ${s1} ${s2}
|
||||
\n\n"
|
||||
(lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
`(("meta.page" . ,(oa-get data "meta.page"))
|
||||
`(("meta.count" . ,count)
|
||||
("meta.page" . ,(oa-get data "meta.page"))
|
||||
("s1" . ,(format "%s" npages))
|
||||
("s2" . ,(format "%s" next-page)))))
|
||||
|
||||
|
||||
(insert
|
||||
(cl-loop for result in results concat
|
||||
(s-format "* ${title}
|
||||
(cl-loop for result in results concat
|
||||
(org-ref--format-template "* ${title}
|
||||
:PROPERTIES:
|
||||
:JOURNAL: ${primary_location.source.display_name}
|
||||
:AUTHOR: ${authors}
|
||||
@@ -989,10 +993,7 @@ ${get-bibtex}
|
||||
- ${oa-related}
|
||||
- ${oa-cited}
|
||||
|
||||
" (lambda (key data)
|
||||
(or (cdr (assoc key data)) ""))
|
||||
|
||||
`(("title" . ,(oa--title result))
|
||||
" `(("title" . ,(oa--title result))
|
||||
("primary_location.source.display_name" . ,(oa-get result "primary_location.source.display_name"))
|
||||
("authors" . ,(oa--authors result))
|
||||
("publication_year" . ,(oa-get result "publication_year"))
|
||||
@@ -1004,7 +1005,7 @@ ${get-bibtex}
|
||||
("oa-cited" . ,(oa--elisp-get-oa-cited-by result))))))
|
||||
|
||||
(insert next-page)
|
||||
|
||||
|
||||
(goto-char (point-min)))
|
||||
(pop-to-buffer buf)))
|
||||
|
||||
@@ -1050,23 +1051,21 @@ Recently published papers are probably missing.
|
||||
:dynamic-collection t))
|
||||
(when (y-or-n-p "Save to file?")
|
||||
(read-file-name "File: "))))
|
||||
|
||||
|
||||
(let* ((data (oa--author entity-id))
|
||||
(works-url (plist-get data :works_api_url))
|
||||
(works-url (plist-get data :works_api_url))
|
||||
(works-data (request-response-data
|
||||
(request works-url
|
||||
:sync t
|
||||
:parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)))))
|
||||
:params (oa--params))))
|
||||
(meta (plist-get works-data :meta))
|
||||
(count (plist-get meta :count))
|
||||
(per-page (plist-get meta :per_page))
|
||||
(pages (/ count per-page))
|
||||
(results (plist-get works-data :results))
|
||||
(current-year (string-to-number (format-time-string "%Y" (current-time))))
|
||||
(current-authors '())
|
||||
purl)
|
||||
(current-authors '()))
|
||||
|
||||
;; Now we need to accumulate the rest of the results from other pages
|
||||
(when (> (mod count per-page) 0) (cl-incf pages))
|
||||
@@ -1077,11 +1076,9 @@ Recently published papers are probably missing.
|
||||
(request works-url
|
||||
:sync t
|
||||
:parser 'oa--response-parser
|
||||
:params `(("mailto" . ,user-mail-address)
|
||||
("api_key" . ,oa-api-key)
|
||||
("page" . ,i))))
|
||||
:params (oa--params `("page" . ,i))))
|
||||
results (append results (plist-get works-data :results))))
|
||||
|
||||
|
||||
;; Now results is a list of your publications. We need to iterate over each
|
||||
;; one, and accumulate author information
|
||||
(cl-loop for result in results do
|
||||
@@ -1093,30 +1090,30 @@ Recently published papers are probably missing.
|
||||
(name-parts (mapcar #'capitalize (split-string name)))
|
||||
(name-string (concat (car (last name-parts)) ", "
|
||||
(string-join (butlast name-parts) " ")))
|
||||
|
||||
(institutions (plist-get authorship :institutions))
|
||||
|
||||
(institutions (plist-get authorship :institutions))
|
||||
(institution (plist-get (car institutions) :display_name)))
|
||||
;; name, institution, contact info, last-active
|
||||
;; we won't have contact info from openalex.
|
||||
(push (list name-string institution "" year
|
||||
(plist-get result :publication_date))
|
||||
current-authors))))))
|
||||
|
||||
|
||||
(setq current-authors (sort current-authors
|
||||
(lambda (a b)
|
||||
"Sort first on name, then on year in descending order"
|
||||
(if (string= (nth 0 a) (nth 0 b))
|
||||
(> (nth 3 a) (nth 3 b))
|
||||
(string< (car a) (car b))))))
|
||||
|
||||
|
||||
;; now filter for unique authors
|
||||
(setq current-authors (cl-loop for group in (seq-group-by (lambda (x)
|
||||
(car x))
|
||||
current-authors)
|
||||
current-authors)
|
||||
collect (cadr group)))
|
||||
|
||||
;; Finally lets fix the year so Excel reads it correctly. I use the publication date
|
||||
(setq current-authors (cl-loop for row in current-authors
|
||||
(setq current-authors (cl-loop for row in current-authors
|
||||
collect
|
||||
(list "A:"
|
||||
(nth 0 row)
|
||||
@@ -1132,7 +1129,7 @@ Recently published papers are probably missing.
|
||||
row)
|
||||
"\t")
|
||||
"\n")))
|
||||
|
||||
|
||||
|
||||
(kill-new (mapconcat (lambda (row)
|
||||
(concat (string-join (mapcar (lambda (x)
|
||||
@@ -1152,7 +1149,7 @@ Recently published papers are probably missing.
|
||||
(interactive)
|
||||
(cl-loop for buf in (buffer-list)
|
||||
do
|
||||
(when (s-starts-with? "*OpenAlex" (buffer-name buf))
|
||||
(when (string-prefix-p "*OpenAlex" (buffer-name buf))
|
||||
(kill-buffer buf))))
|
||||
|
||||
|
||||
@@ -1165,7 +1162,7 @@ Operates on headings with a DOI property."
|
||||
(lambda ()
|
||||
(kill-new (org-entry-get (point) "DOI"))
|
||||
(doi-utils-add-bibtex-entry-from-doi
|
||||
(doi-utils-maybe-doi-from-region-or-current-kill)
|
||||
(doi-utils-maybe-doi-from-region-or-current-kill)
|
||||
bibfile))
|
||||
"DOI<>\"\"")))
|
||||
|
||||
|
||||
@@ -26,10 +26,7 @@
|
||||
|
||||
;;; Code:
|
||||
(require 'bibtex)
|
||||
(require 'dash)
|
||||
(require 'f)
|
||||
(require 'org)
|
||||
(require 's)
|
||||
(require 'org-ref-utils)
|
||||
(require 'parsebib)
|
||||
|
||||
@@ -42,6 +39,7 @@
|
||||
|
||||
(declare-function parsebib-find-bibtex-dialect "parsebib")
|
||||
(declare-function org-ref-clean-bibtex-entry "org-ref-core")
|
||||
(declare-function org-ref-normalize-bibtex-completion-bibliography "org-ref-utils")
|
||||
;; this is a C function
|
||||
(declare-function libxml-parse-xml-region "xml")
|
||||
|
||||
@@ -85,7 +83,7 @@
|
||||
(with-current-buffer
|
||||
(url-retrieve-synchronously (format "https://ui.adsabs.harvard.edu/abs/%s/exportcitation" arxiv-bibliographic-code))
|
||||
(when (re-search-forward
|
||||
"<textarea.*>\\(.*\\(?:\n.*\\)*?\\(?:\n\\s-*\n\\|\\'\\)\\)</textarea>"
|
||||
"<textarea.*>\\(.*\\(?:\n.*\\)*?\\)\\(?:\n\\s-*\n\\)</textarea>"
|
||||
nil t)
|
||||
(xml-substitute-special (match-string 1)))))
|
||||
|
||||
@@ -121,13 +119,13 @@ Returns a formatted BibTeX entry."
|
||||
(match-beginning 0))
|
||||
(point-max)))
|
||||
(entry (assq 'entry parse-tree))
|
||||
(authors (--map (nth 2 (nth 2 it))
|
||||
(--filter (and (listp it) (eq (car it) 'author)) entry)))
|
||||
(authors (mapcar (lambda (it) (nth 2 (nth 2 it)))
|
||||
(seq-filter (lambda (it) (and (listp it) (eq (car it) 'author))) entry)))
|
||||
(year (format-time-string "%Y" (date-to-time (nth 2 (assq 'published entry)))))
|
||||
(title (nth 2 (assq 'title entry)))
|
||||
(names (arxiv-bibtexify-authors authors))
|
||||
(category (cdar (nth 1 (assq 'primary_category entry))))
|
||||
(abstract (s-trim (nth 2 (assq 'summary entry))))
|
||||
(abstract (string-trim (nth 2 (assq 'summary entry))))
|
||||
(url (nth 2 (assq 'id entry)))
|
||||
(temp-bibtex (format arxiv-entry-format-string "" title names year arxiv-number category abstract url))
|
||||
(key (with-temp-buffer
|
||||
@@ -145,9 +143,12 @@ Returns a formatted BibTeX entry."
|
||||
|
||||
(defun arxiv-bibtexify-authors (authors)
|
||||
"Return names in 'SURNAME, FIRST NAME' format from AUTHORS list."
|
||||
(s-join " and "
|
||||
(--map (concat (-last-item it) ", " (s-join " " (-remove-last 'stringp it)))
|
||||
(--map (s-split " +" it) authors))))
|
||||
(string-join
|
||||
(mapcar (lambda (it)
|
||||
(concat (car (last it)) ", "
|
||||
(string-join (butlast it) " ")))
|
||||
(mapcar (lambda (it) (split-string it " +")) authors))
|
||||
" and "))
|
||||
|
||||
|
||||
(defun arxiv-maybe-arxiv-id-from-current-kill ()
|
||||
@@ -166,22 +167,22 @@ Returns a formatted BibTeX entry."
|
||||
(;; check if current-kill looks like an arxiv ID
|
||||
;; if so, return it
|
||||
;; Ex: 1304.4404v2
|
||||
(s-match (concat "^" arxiv-id-regexp) the-current-kill)
|
||||
(org-ref--string-match (concat "^" arxiv-id-regexp) the-current-kill)
|
||||
the-current-kill)
|
||||
(;; check if current-kill looks like an arxiv cite
|
||||
;; if so, remove the prefix and return
|
||||
;; Ex: arXiv:1304.4404v2 --> 1304.4404v2
|
||||
(s-match (concat arxiv-cite-prefix-regexp arxiv-id-regexp "$") the-current-kill)
|
||||
(org-ref--string-match (concat arxiv-cite-prefix-regexp arxiv-id-regexp "$") the-current-kill)
|
||||
(replace-regexp-in-string arxiv-cite-prefix-regexp "" the-current-kill))
|
||||
(;; check if current-kill looks like an arxiv url
|
||||
;; if so, remove the url prefix and return
|
||||
;; Ex: https://arxiv.org/abs/1304.4404 --> 1304.4404
|
||||
(s-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "$") the-current-kill)
|
||||
(org-ref--string-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "$") the-current-kill)
|
||||
(replace-regexp-in-string arxiv-url-prefix-regexp "" the-current-kill))
|
||||
(;; check if current-kill looks like an arxiv PDF url
|
||||
;; if so, remove the url prefix, the .pdf suffix, and return
|
||||
;; Ex: https://arxiv.org/pdf/1304.4404.pdf --> 1304.4404
|
||||
(s-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "\\.pdf$") the-current-kill)
|
||||
(org-ref--string-match (concat arxiv-url-prefix-regexp arxiv-id-regexp "\\.pdf$") the-current-kill)
|
||||
(replace-regexp-in-string arxiv-url-prefix-regexp "" (substring the-current-kill 0 (- (length the-current-kill) 4))))
|
||||
;; otherwise, return nil
|
||||
(t
|
||||
@@ -199,10 +200,8 @@ Returns a formatted BibTeX entry."
|
||||
;; now get the bibfile to add it to
|
||||
(completing-read
|
||||
"Bibfile: "
|
||||
(append (f-entries "." (lambda (f) (f-ext? f "bib")))
|
||||
(if (stringp bibtex-completion-bibliography)
|
||||
(list bibtex-completion-bibliography)
|
||||
bibtex-completion-bibliography)))))
|
||||
(append (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib")))
|
||||
(org-ref-normalize-bibtex-completion-bibliography)))))
|
||||
(save-window-excursion
|
||||
(find-file bibfile)
|
||||
(goto-char (point-max))
|
||||
@@ -245,8 +244,8 @@ key."
|
||||
;; now get the bibfile to add it to
|
||||
(completing-read
|
||||
"Bibfile: "
|
||||
(append (f-entries "." (lambda (f) (f-ext? f "bib")))
|
||||
bibtex-completion-bibliography))
|
||||
(append (org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib")))
|
||||
(org-ref-normalize-bibtex-completion-bibliography)))
|
||||
(cond
|
||||
((stringp bibtex-completion-library-path)
|
||||
bibtex-completion-library-path)
|
||||
|
||||
@@ -257,11 +257,11 @@ Returns `org-ref-bst-styles' or sets it and returns it."
|
||||
(setq org-ref-bst-styles
|
||||
(mapcar 'file-name-nondirectory
|
||||
(mapcar 'file-name-sans-extension
|
||||
(-flatten
|
||||
(org-ref--flatten-list
|
||||
(mapcar (lambda (path)
|
||||
(setq path (replace-regexp-in-string "!" "" path))
|
||||
(when (file-directory-p path)
|
||||
(f-entries path (lambda (f) (f-ext? f "bst")))))
|
||||
(org-ref--directory-files path (lambda (f) (org-ref--file-ext-p f "bst")))))
|
||||
(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'")
|
||||
|
||||
@@ -52,18 +52,16 @@
|
||||
;; org-ref-replace-nonascii :: replace nonascii characters in a bibtex
|
||||
;; entry. Replacements are in `org-ref-nonascii-latex-replacements'.
|
||||
;;
|
||||
;; ** hydra menu for bibtex files
|
||||
;; `org-ref-bibtex-hydra/body' gives a hydra menu to a lot of useful functions.
|
||||
;; `org-ref-bibtex-new-entry/body' gives a hydra menu to add new bibtex entries.
|
||||
;; `org-ref-bibtex-file/body' gives a hydra menu of actions for the bibtex file
|
||||
;; ** Transient menu for bibtex files
|
||||
;; `org-ref-bibtex-entry-menu' gives access to many useful functions.
|
||||
;; `org-ref-bibtex-new-entry-menu' presents commands to add new bibtex entries.
|
||||
;; `org-ref-bibtex-file-menu' collects actions for entire bibtex files.
|
||||
;;
|
||||
;;; Code
|
||||
|
||||
(require 'bibtex)
|
||||
(require 'dash)
|
||||
(require 'hydra)
|
||||
(require 'transient)
|
||||
(require 'message)
|
||||
(require 's)
|
||||
(require 'doi-utils)
|
||||
(require 'avy)
|
||||
(require 'sgml-mode)
|
||||
@@ -419,7 +417,7 @@ START and END allow you to use this with `bibtex-map-entries'"
|
||||
(lambda (row)
|
||||
(cons (nth 2 row) (nth 0 row)))
|
||||
org-ref-bibtex-journal-abbreviations))
|
||||
(journal (s-trim (bibtex-autokey-get-field "journal")))
|
||||
(journal (string-trim (bibtex-autokey-get-field "journal")))
|
||||
(bstring (or
|
||||
(cdr (assoc journal full-names))
|
||||
(cdr (assoc journal abbrev-names)))))
|
||||
@@ -512,19 +510,18 @@ books."
|
||||
word)
|
||||
;; these words should not be capitalized, unless they
|
||||
;; are the first word
|
||||
((-contains? org-ref-lower-case-words
|
||||
(s-downcase word))
|
||||
(s-downcase word))
|
||||
((member (downcase word) org-ref-lower-case-words)
|
||||
(downcase word))
|
||||
;; Words that are quoted
|
||||
((s-starts-with? "\"" word)
|
||||
(concat "\"" (s-capitalize (substring word 1))))
|
||||
((string-prefix-p "\"" word)
|
||||
(concat "\"" (capitalize (substring word 1))))
|
||||
(t
|
||||
(s-capitalize word))))
|
||||
(capitalize word))))
|
||||
words))
|
||||
|
||||
;; Check if first word should be capitalized
|
||||
(when (-contains? org-ref-lower-case-words (car words))
|
||||
(setf (car words) (s-capitalize (car words))))
|
||||
(when (member (car words) org-ref-lower-case-words)
|
||||
(setf (car words) (capitalize (car words))))
|
||||
|
||||
(setq title (mapconcat 'identity words " "))
|
||||
|
||||
@@ -578,11 +575,11 @@ all the title entries in articles."
|
||||
;; LaTeX or protected words
|
||||
(string-match "\\$\\|{\\|}\\|\\\\" word)
|
||||
word
|
||||
(s-downcase word)))
|
||||
(downcase word)))
|
||||
words))
|
||||
|
||||
;; capitalize first word
|
||||
(setf (car words) (s-capitalize (car words)))
|
||||
(setf (car words) (capitalize (car words)))
|
||||
|
||||
;; join the words
|
||||
(setq title (mapconcat 'identity words " "))
|
||||
@@ -816,132 +813,137 @@ a directory. Optional PREFIX argument toggles between
|
||||
|
||||
;;* Hydra menus
|
||||
;;** Hydra menu for bibtex entries
|
||||
;; hydra menu for actions on bibtex entries
|
||||
(defhydra org-ref-bibtex-hydra (:color blue :hint nil)
|
||||
"Bibtex actions:
|
||||
"
|
||||
;; Open-like actions
|
||||
("p" org-ref-open-bibtex-pdf "PDF" :column "Open")
|
||||
("n" org-ref-open-bibtex-notes "Notes" :column "Open")
|
||||
("b" org-ref-open-in-browser "URL" :column "Open")
|
||||
|
||||
;; edit/modify
|
||||
("K" (lambda ()
|
||||
(interactive)
|
||||
(org-ref-set-bibtex-keywords
|
||||
(read-string "Keywords: "
|
||||
(bibtex-autokey-get-field "keywords"))
|
||||
t))
|
||||
"Keywords" :column "Edit")
|
||||
("a" org-ref-replace-nonascii "Replace nonascii" :column "Edit")
|
||||
("s" org-ref-sort-bibtex-entry "Sort fields" :column "Edit")
|
||||
("T" org-ref-title-case-article "Title case" :column "Edit")
|
||||
("S" org-ref-sentence-case-article "Sentence case" :column "Edit")
|
||||
("U" (doi-utils-update-bibtex-entry-from-doi (org-ref-bibtex-entry-doi)) "Update entry" :column "Edit")
|
||||
("u" doi-utils-update-field "Update field" :column "Edit" :color red)
|
||||
("<backspace>" (cl--set-buffer-substring (line-beginning-position) (+ 1 (line-end-position)) "")
|
||||
"Delete line" :column "Edit" :color red)
|
||||
("d" bibtex-kill-entry "Kill entry" :column "Edit")
|
||||
("L" org-ref-clean-bibtex-entry "Clean entry" :column "Edit")
|
||||
("A" org-ref-bibtex-assoc-pdf-with-entry "Add pdf" :column "Edit")
|
||||
("r" (lambda ()
|
||||
(interactive)
|
||||
(bibtex-beginning-of-entry)
|
||||
(bibtex-kill-entry)
|
||||
(find-file (completing-read
|
||||
"Bibtex file: "
|
||||
(append bibtex-completion-bibliography
|
||||
(f-entries "." (lambda (f) (f-ext? f "bib"))))))
|
||||
(goto-char (point-max))
|
||||
(bibtex-yank)
|
||||
(save-buffer)
|
||||
(kill-buffer))
|
||||
"Refile entry" :column "Edit")
|
||||
|
||||
;; www
|
||||
("P" org-ref-bibtex-pubmed "Pubmed" :column "WWW")
|
||||
("w" org-ref-bibtex-wos "WOS" :column "WWW")
|
||||
("c" org-ref-bibtex-wos-citing "WOS citing" :column "WWW")
|
||||
("a" org-ref-bibtex-wos-related "WOS related" :column "WWW")
|
||||
("R" org-ref-bibtex-crossref "Crossref" :column "WWW")
|
||||
("g" org-ref-bibtex-google-scholar "Google Scholar" :column "WWW")
|
||||
("e" org-ref-email-bibtex-entry "Email" :column "WWW")
|
||||
|
||||
|
||||
;; Copy
|
||||
("o" (lambda ()
|
||||
(interactive)
|
||||
(bibtex-copy-entry-as-kill)
|
||||
(message "Use %s to paste the entry"
|
||||
(substitute-command-keys (format "\\[bibtex-yank]"))))
|
||||
"Copy entry" :column "Copy")
|
||||
|
||||
("y" (save-excursion
|
||||
(bibtex-beginning-of-entry)
|
||||
(when (looking-at bibtex-entry-maybe-empty-head)
|
||||
(kill-new (bibtex-key-in-head))))
|
||||
"Copy key" :column "Copy")
|
||||
|
||||
("f" (save-excursion
|
||||
(bibtex-beginning-of-entry)
|
||||
(kill-new (bibtex-completion-apa-format-reference
|
||||
(cdr (assoc "=key=" (bibtex-parse-entry t))))))
|
||||
"Formatted entry" :column "Copy")
|
||||
|
||||
;; Navigation
|
||||
("[" org-ref-bibtex-next-entry "Next entry" :column "Navigation" :color red)
|
||||
("]" org-ref-bibtex-previous-entry "Previous entry" :column "Navigation" :color red)
|
||||
("<down>" next-line "Next line" :column "Navigation" :color red)
|
||||
("<up>" previous-line "Previous line" :column "Navigation" :color red)
|
||||
("<next>" scroll-up-command "Scroll up" :column "Navigation" :color red)
|
||||
("<prior>" scroll-down-command "Scroll down" :column "Navigation" :color red)
|
||||
("v" org-ref-bibtex-visible-entry "Visible entry" :column "Navigation" :color red)
|
||||
("V" org-ref-bibtex-visible-field "Visible field" :column "Navigation" :color red)
|
||||
|
||||
|
||||
;; Miscellaneous
|
||||
("F" org-ref-bibtex-file/body "File hydra" :column "Misc")
|
||||
("N" org-ref-bibtex-new-entry/body "New entry" :column "Misc")
|
||||
("q" nil))
|
||||
;; transient menu for actions on bibtex entries
|
||||
(transient-define-prefix org-ref-bibtex-entry-menu ()
|
||||
"Bibtex actions."
|
||||
[["Open"
|
||||
("p" "PDF" org-ref-open-bibtex-pdf)
|
||||
("n" "Notes" org-ref-open-bibtex-notes)
|
||||
("b" "URL" org-ref-open-in-browser)]
|
||||
["Edit"
|
||||
("K" "Keywords" (lambda ()
|
||||
(interactive)
|
||||
(org-ref-set-bibtex-keywords
|
||||
(read-string "Keywords: "
|
||||
(bibtex-autokey-get-field "keywords"))
|
||||
t)))
|
||||
("a" "Replace nonascii" org-ref-replace-nonascii)
|
||||
("s" "Sort fields" org-ref-sort-bibtex-entry)
|
||||
("T" "Title case" org-ref-title-case-article)
|
||||
("S" "Sentence case" org-ref-sentence-case-article)
|
||||
("U" "Update entry" (lambda ()
|
||||
(interactive)
|
||||
(doi-utils-update-bibtex-entry-from-doi (org-ref-bibtex-entry-doi))))
|
||||
("u" "Update field" doi-utils-update-field :transient t)
|
||||
("<backspace>" "Delete line" (lambda ()
|
||||
(interactive)
|
||||
(cl--set-buffer-substring
|
||||
(line-beginning-position)
|
||||
(1+ (line-end-position))
|
||||
"")) :transient t)
|
||||
("d" "Kill entry" bibtex-kill-entry)
|
||||
("L" "Clean entry" org-ref-clean-bibtex-entry)
|
||||
("A" "Add pdf" org-ref-bibtex-assoc-pdf-with-entry)
|
||||
("r" "Refile entry" (lambda ()
|
||||
(interactive)
|
||||
(bibtex-beginning-of-entry)
|
||||
(bibtex-kill-entry)
|
||||
(find-file (completing-read
|
||||
"Bibtex file: "
|
||||
(append bibtex-completion-bibliography
|
||||
(org-ref--directory-files "." (lambda (f) (org-ref--file-ext-p f "bib"))))))
|
||||
(goto-char (point-max))
|
||||
(bibtex-yank)
|
||||
(save-buffer)
|
||||
(kill-buffer)))]
|
||||
["WWW"
|
||||
("P" "Pubmed" org-ref-bibtex-pubmed)
|
||||
("w" "WOS" org-ref-bibtex-wos)
|
||||
("c" "WOS citing" org-ref-bibtex-wos-citing)
|
||||
("a" "WOS related" org-ref-bibtex-wos-related)
|
||||
("R" "Crossref" org-ref-bibtex-crossref)
|
||||
("g" "Google Scholar" org-ref-bibtex-google-scholar)
|
||||
("e" "Email" org-ref-email-bibtex-entry)]
|
||||
["Copy"
|
||||
("o" "Copy entry" (lambda ()
|
||||
(interactive)
|
||||
(bibtex-copy-entry-as-kill)
|
||||
(message "Use %s to paste the entry"
|
||||
(substitute-command-keys (format "\\[bibtex-yank]")))))
|
||||
("y" "Copy key" (lambda ()
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(bibtex-beginning-of-entry)
|
||||
(when (looking-at bibtex-entry-maybe-empty-head)
|
||||
(kill-new (bibtex-key-in-head))))))
|
||||
("f" "Formatted entry" (lambda ()
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(bibtex-beginning-of-entry)
|
||||
(kill-new (bibtex-completion-apa-format-reference
|
||||
(cdr (assoc "=key=" (bibtex-parse-entry t))))))))]
|
||||
["Navigation"
|
||||
("[" "Next entry" org-ref-bibtex-next-entry :transient t)
|
||||
("]" "Previous entry" org-ref-bibtex-previous-entry :transient t)
|
||||
("<down>" "Next line" next-line :transient t)
|
||||
("<up>" "Previous line" previous-line :transient t)
|
||||
("<next>" "Scroll up" scroll-up-command :transient t)
|
||||
("<prior>" "Scroll down" scroll-down-command :transient t)
|
||||
("v" "Visible entry" org-ref-bibtex-visible-entry :transient t)
|
||||
("V" "Visible field" org-ref-bibtex-visible-field :transient t)]
|
||||
["Misc"
|
||||
("F" "File menu" org-ref-bibtex-file-menu)
|
||||
("N" "New entry" org-ref-bibtex-new-entry-menu)
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
|
||||
(declare-function biblio-lookup "biblio")
|
||||
(declare-function arxiv-add-bibtex-entry "org-ref-arxiv")
|
||||
(declare-function doi-insert-bibtex "doi-utils")
|
||||
|
||||
;;** Hydra menu for new bibtex entries
|
||||
;; A hydra for adding new bibtex entries.
|
||||
(defhydra org-ref-bibtex-new-entry (:color blue)
|
||||
"New Bibtex entry:"
|
||||
("d" doi-insert-bibtex "from DOI" :column "Automatic")
|
||||
("c" crossref-add-bibtex-entry "from Crossref" :column "Automatic")
|
||||
("a" arxiv-add-bibtex-entry "From Arxiv" :column "Automatic")
|
||||
("b" biblio-lookup "From biblio" :column "Automatic")
|
||||
;; Bibtex types
|
||||
("ma" bibtex-Article "Article" :column "Manual")
|
||||
("mb" bibtex-Book "Book" :column "Manual")
|
||||
("mi" bibtex-InBook "In book" :column "Manual")
|
||||
("ml" bibtex-Booklet "Booklet" :column "Manual")
|
||||
("mP" bibtex-Proceedings "Proceedings" :column "Manual")
|
||||
("mp" bibtex-InProceedings "In proceedings" :column "Manual")
|
||||
("mm" bibtex-Misc "Misc." :column "Manual")
|
||||
("mM" bibtex-Manual "Manual" :column "Manual")
|
||||
("mT" bibtex-PhdThesis "PhD Thesis" :column "Manual")
|
||||
("mt" bibtex-MastersThesis "MS Thesis" :column "Manual")
|
||||
("mR" bibtex-TechReport "Report" :column "Manual")
|
||||
("mu" bibtex-Unpublished "unpublished" :column "Manual")
|
||||
("mc" bibtex-InCollection "Article in collection" :column "Manual")
|
||||
("q" nil "quit"))
|
||||
;;** Transient menu for new bibtex entries
|
||||
;; A transient for adding new bibtex entries.
|
||||
(transient-define-prefix org-ref-bibtex-new-entry-menu ()
|
||||
"New Bibtex entry."
|
||||
[["Automatic"
|
||||
("d" "from DOI" doi-insert-bibtex)
|
||||
("c" "from Crossref" crossref-add-bibtex-entry)
|
||||
("a" "From Arxiv" arxiv-add-bibtex-entry)
|
||||
("b" "From biblio" biblio-lookup)]
|
||||
["Manual"
|
||||
("ma" "Article" bibtex-Article)
|
||||
("mb" "Book" bibtex-Book)
|
||||
("mi" "In book" bibtex-InBook)
|
||||
("ml" "Booklet" bibtex-Booklet)
|
||||
("mP" "Proceedings" bibtex-Proceedings)
|
||||
("mp" "In proceedings" bibtex-InProceedings)
|
||||
("mm" "Misc." bibtex-Misc)
|
||||
("mM" "Manual" bibtex-Manual)
|
||||
("mT" "PhD Thesis" bibtex-PhdThesis)
|
||||
("mt" "MS Thesis" bibtex-MastersThesis)
|
||||
("mR" "Report" bibtex-TechReport)
|
||||
("mu" "unpublished" bibtex-Unpublished)
|
||||
("mc" "Article in collection" bibtex-InCollection)
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
|
||||
;;** Hydra menu of functions to act on a bibtex file.
|
||||
(defhydra org-ref-bibtex-file (:color blue)
|
||||
"Bibtex file functions: "
|
||||
("v" bibtex-validate "Validate entries")
|
||||
("s" bibtex-sort-buffer "Sort entries")
|
||||
("r" bibtex-reformat "Reformat entries")
|
||||
("c" bibtex-count-entries "Count entries")
|
||||
("p" org-ref-build-full-bibliography "PDF bibliography"))
|
||||
;;** Transient menu of functions to act on a bibtex file.
|
||||
(transient-define-prefix org-ref-bibtex-file-menu ()
|
||||
"Bibtex file functions."
|
||||
[["Commands"
|
||||
("v" "Validate entries" bibtex-validate)
|
||||
("s" "Sort entries" bibtex-sort-buffer)
|
||||
("r" "Reformat entries" bibtex-reformat)
|
||||
("c" "Count entries" bibtex-count-entries)
|
||||
("p" "PDF bibliography" org-ref-build-full-bibliography)
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
(define-obsolete-function-alias 'org-ref-bibtex-hydra/body
|
||||
#'org-ref-bibtex-entry-menu "3.1")
|
||||
(define-obsolete-function-alias 'org-ref-bibtex-new-entry/body
|
||||
#'org-ref-bibtex-new-entry-menu "3.1")
|
||||
(define-obsolete-function-alias 'org-ref-bibtex-file/body
|
||||
#'org-ref-bibtex-file-menu "3.1")
|
||||
|
||||
|
||||
;;* Email a bibtex entry
|
||||
@@ -1022,7 +1024,7 @@ keywords. Optional argument ARG prefix arg to replace keywords."
|
||||
(cl-loop for buffer in (buffer-list)
|
||||
do
|
||||
(with-current-buffer buffer
|
||||
(when (and (buffer-file-name) (f-ext? (buffer-file-name) "bib"))
|
||||
(when (and (buffer-file-name) (org-ref--file-ext-p (buffer-file-name) "bib"))
|
||||
(save-buffer)))))
|
||||
|
||||
|
||||
@@ -1150,7 +1152,7 @@ will clobber the file."
|
||||
|
||||
;;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)
|
||||
(sort (cl-remove-if (lambda(x) (member x field-order)) entry-fields)
|
||||
'string<))
|
||||
|
||||
(save-restriction
|
||||
@@ -1170,7 +1172,7 @@ will clobber the file."
|
||||
(cl-loop for (f . v) in entry concat
|
||||
(when (string= f field)
|
||||
(format "%s = %s,\n" f v))))
|
||||
(-uniq other-fields) "\n")
|
||||
(delete-dups other-fields) "\n")
|
||||
"\n}"))
|
||||
(bibtex-search-entry key)
|
||||
(bibtex-fill-entry)
|
||||
@@ -1290,7 +1292,7 @@ If optional NEW-YEAR set it to that, otherwise prompt for it."
|
||||
|
||||
|
||||
(defun orcb-& ()
|
||||
"Replace naked & with \& in a bibtex entry."
|
||||
"Replace naked & with \\& in a bibtex entry."
|
||||
(save-restriction
|
||||
(bibtex-narrow-to-entry)
|
||||
(bibtex-beginning-of-entry)
|
||||
@@ -1365,7 +1367,7 @@ If not, issue a warning."
|
||||
(journal (cdr (assoc "journal" entry))))
|
||||
(when (null journal)
|
||||
(warn "Unable to get journal for this entry."))
|
||||
(unless (member journal (-flatten org-ref-bibtex-journal-abbreviations))
|
||||
(unless (member journal (org-ref--flatten-list org-ref-bibtex-journal-abbreviations))
|
||||
(message "Journal \"%s\" not found in org-ref-bibtex-journal-abbreviations." journal))))))
|
||||
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
;; indicate the pre/post-note structure. They also have tooltips that show
|
||||
;; information from the bibtex entry.
|
||||
;;
|
||||
;; Each link is functional, and clicking on one will open a hydra menu
|
||||
;; `org-ref-citation-hydra/body' of actions that range from opening the bibtex
|
||||
;; Each link is functional, and clicking on one will open a transient menu
|
||||
;; `org-ref-citation-menu' of actions that range from opening the bibtex
|
||||
;; entry, notes, pdf or associated URL, to searching the internet for related
|
||||
;; articles.
|
||||
;;
|
||||
@@ -48,7 +48,7 @@
|
||||
;;
|
||||
;;; Code:
|
||||
(require 'org-keys)
|
||||
(require 'hydra)
|
||||
(require 'transient)
|
||||
(require 'xref)
|
||||
(eval-when-compile (require 'subr-x))
|
||||
|
||||
@@ -340,14 +340,14 @@ to a path string."
|
||||
(string-join (cl-loop for ref in (plist-get data :references) collect (plist-get ref :key)) ","))
|
||||
(3
|
||||
(concat
|
||||
(when-let (prefix (plist-get data :prefix)) (concat prefix ";"))
|
||||
(when-let* ((prefix (plist-get data :prefix))) (concat prefix ";"))
|
||||
(string-join (cl-loop for ref in (plist-get data :references) collect
|
||||
(concat
|
||||
(plist-get ref :prefix)
|
||||
"&" (plist-get ref :key)
|
||||
(plist-get ref :suffix)))
|
||||
";")
|
||||
(when-let (suffix (plist-get data :suffix)) (concat ";" suffix))))))
|
||||
(when-let* ((suffix (plist-get data :suffix))) (concat ";" suffix))))))
|
||||
|
||||
|
||||
;; * Activating citation links
|
||||
@@ -360,19 +360,6 @@ to a path string."
|
||||
(defvar bibtex-completion-bibliography)
|
||||
(defvar bibtex-completion-display-formats-internal)
|
||||
|
||||
;; (defun org-ref-valid-keys ()
|
||||
;; "Return a list of valid bibtex keys for this buffer.
|
||||
;; This is used a lot in `org-ref-cite-activate' so it needs to be
|
||||
;; fast, but also up to date."
|
||||
;; ;; this seems to be needed, but we don't want to do this every time
|
||||
;; (unless bibtex-completion-display-formats-internal
|
||||
;; (bibtex-completion-init))
|
||||
|
||||
;; (let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
|
||||
;; (cl-loop for entry in (bibtex-completion-candidates)
|
||||
;; collect
|
||||
;; (cdr (assoc "=key=" (cdr entry))))))
|
||||
|
||||
|
||||
(defun org-ref-valid-keys ()
|
||||
"Return a list of valid bibtex keys for this buffer.
|
||||
@@ -380,6 +367,8 @@ This is used a lot in `org-ref-cite-activate' so it needs to be
|
||||
fast, but also up to date."
|
||||
|
||||
;; this seems to be needed, but we don't want to do this every time
|
||||
;; I found when bibtex-completion-display-formats-internal is nil
|
||||
;; we have to run this init function
|
||||
(unless bibtex-completion-display-formats-internal
|
||||
(bibtex-completion-init))
|
||||
|
||||
@@ -394,7 +383,9 @@ fast, but also up to date."
|
||||
for file in files
|
||||
append (cddr (assoc file bibtex-completion-cache)))
|
||||
collect (cdr (assoc "=key=" (cdr entry))))
|
||||
;; you need to get a cache because one or more of the files was not in the cache.
|
||||
;; you need to get a cache because one or more of the files was not in the
|
||||
;; cache. The cache should be automatically made by
|
||||
;; bibtex-completion-candidates
|
||||
(let ((bibtex-completion-bibliography files))
|
||||
(cl-loop for entry in (bibtex-completion-candidates)
|
||||
collect
|
||||
@@ -405,11 +396,15 @@ fast, but also up to date."
|
||||
(defvar-local org-ref-valid-keys-cache nil)
|
||||
|
||||
(defun org-ref-valid-keys-cached ()
|
||||
"Update `org-ref-valid-keys-cache` only when files changed."
|
||||
"Update `org-ref-valid-keys-cache` only when files changed or it is empty.
|
||||
Returns a hash-table you can use to test key validity.
|
||||
|
||||
(gethash key (org-ref-valid-keys-cached)"
|
||||
|
||||
(let ((local-hashes (cons bibtex-completion-bibliography
|
||||
(mapcar 'cadr bibtex-completion-cache))))
|
||||
(when (not (equal local-hashes org-ref-valid-keys-hashes))
|
||||
(when (or (null org-ref-valid-keys-cache)
|
||||
(not (equal local-hashes org-ref-valid-keys-hashes)))
|
||||
(setq-local org-ref-valid-keys-hashes local-hashes)
|
||||
(setq-local org-ref-valid-keys-cache (make-hash-table :test 'equal))
|
||||
(cl-loop for entry in (org-ref-valid-keys)
|
||||
@@ -427,7 +422,7 @@ PATH has the citations in it."
|
||||
;; path containing @ which makes it likely to be an org-cite. Maybe
|
||||
;; a text property is better, in case this is an issue in the
|
||||
;; future.
|
||||
(not (s-contains-p "@" path)))
|
||||
(not (string-match-p (regexp-quote "@") path)))
|
||||
(let* ((valid-keys (org-ref-valid-keys))
|
||||
valid-key
|
||||
substrings)
|
||||
@@ -522,64 +517,67 @@ PATH has the citations in it."
|
||||
|
||||
(declare-function org-ref-get-bibtex-key-and-file "org-ref-core")
|
||||
|
||||
(defhydra org-ref-citation-hydra (:color blue :hint nil)
|
||||
"Citation actions
|
||||
"
|
||||
("o" org-ref-open-citation-at-point "Bibtex" :column "Open")
|
||||
("p" org-ref-open-pdf-at-point "PDF" :column "Open")
|
||||
("n" org-ref-open-notes-at-point "Notes" :column "Open")
|
||||
("u" org-ref-open-url-at-point "URL" :column "Open")
|
||||
|
||||
;; WWW actions
|
||||
("ww" org-ref-wos-at-point "WOS" :column "WWW")
|
||||
("wr" org-ref-wos-related-at-point "WOS related" :column "WWW")
|
||||
("wc" org-ref-wos-citing-at-point "WOS citing" :column "WWW")
|
||||
("wg" org-ref-google-scholar-at-point "Google Scholar" :column "WWW")
|
||||
("wp" org-ref-pubmed-at-point "Pubmed" :column "WWW")
|
||||
("wf" org-ref-crossref-at-point "Crossref" :column "WWW")
|
||||
("wb" org-ref-biblio-at-point "Biblio" :column "WWW")
|
||||
("e" org-ref-email-at-point "Email" :column "WWW")
|
||||
|
||||
;; Copyish actions
|
||||
("K" (save-window-excursion
|
||||
(let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
|
||||
(bibtex-completion-show-entry (list (org-ref-get-bibtex-key-under-cursor)))
|
||||
(bibtex-copy-entry-as-kill)
|
||||
(kill-new (pop bibtex-entry-kill-ring))))
|
||||
"Copy bibtex" :column "Copy")
|
||||
("a" org-ref-add-pdf-at-point "add pdf to library" :column "Copy")
|
||||
("k" (kill-new (car (org-ref-get-bibtex-key-and-file))) "Copy key" :column "Copy")
|
||||
("f" (kill-new (bibtex-completion-apa-format-reference
|
||||
(org-ref-get-bibtex-key-under-cursor)))
|
||||
"Copy formatted" :column "Copy")
|
||||
("h" (kill-new
|
||||
(format "* %s\n\n cite:&%s"
|
||||
(bibtex-completion-apa-format-reference
|
||||
(org-ref-get-bibtex-key-under-cursor))
|
||||
(car (org-ref-get-bibtex-key-and-file))))
|
||||
"Copy org heading"
|
||||
:column "Copy")
|
||||
|
||||
;; Editing actions
|
||||
("<left>" org-ref-cite-shift-left "Shift left" :color red :column "Edit")
|
||||
("<right>" org-ref-cite-shift-right "Shift right" :color red :column "Edit")
|
||||
("<up>" org-ref-sort-citation-link "Sort by year" :column "Edit")
|
||||
("i" (funcall org-ref-insert-cite-function) "Insert cite" :column "Edit")
|
||||
("t" org-ref-change-cite-type "Change cite type" :column "Edit")
|
||||
("d" org-ref-delete-citation-at-point "Delete at point" :column "Edit")
|
||||
("r" org-ref-replace-citation-at-point "Replace cite" :column "Edit")
|
||||
("P" org-ref-edit-pre-post-notes "Edit pre/suffix" :column "Edit")
|
||||
|
||||
;; Navigation
|
||||
("[" org-ref-previous-key "Previous key" :column "Navigation" :color red)
|
||||
("]" org-ref-next-key "Next key" :column "Navigation" :color red)
|
||||
("v" org-ref-jump-to-visible-key "Visible key" :column "Navigation" :color red)
|
||||
("q" nil "Quit"))
|
||||
(transient-define-prefix org-ref-citation-menu ()
|
||||
"Citation actions."
|
||||
[["Open"
|
||||
("o" "Bibtex" org-ref-open-citation-at-point)
|
||||
("p" "PDF" org-ref-open-pdf-at-point)
|
||||
("n" "Notes" org-ref-open-notes-at-point)
|
||||
("u" "URL" org-ref-open-url-at-point)]
|
||||
["WWW"
|
||||
("ww" "WOS" org-ref-wos-at-point)
|
||||
("wr" "WOS related" org-ref-wos-related-at-point)
|
||||
("wc" "WOS citing" org-ref-wos-citing-at-point)
|
||||
("wg" "Google Scholar" org-ref-google-scholar-at-point)
|
||||
("wp" "Pubmed" org-ref-pubmed-at-point)
|
||||
("wf" "Crossref" org-ref-crossref-at-point)
|
||||
("wb" "Biblio" org-ref-biblio-at-point)
|
||||
("e" "Email" org-ref-email-at-point)]
|
||||
["Copy"
|
||||
("K" "Copy bibtex" (lambda ()
|
||||
(interactive)
|
||||
(save-window-excursion
|
||||
(let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
|
||||
(bibtex-completion-show-entry (list (org-ref-get-bibtex-key-under-cursor)))
|
||||
(bibtex-copy-entry-as-kill)
|
||||
(kill-new (pop bibtex-entry-kill-ring))))))
|
||||
("a" "Add pdf to library" org-ref-add-pdf-at-point)
|
||||
("k" "Copy key" (lambda ()
|
||||
(interactive)
|
||||
(kill-new (car (org-ref-get-bibtex-key-and-file)))))
|
||||
("f" "Copy formatted" (lambda ()
|
||||
(interactive)
|
||||
(kill-new (bibtex-completion-apa-format-reference
|
||||
(org-ref-get-bibtex-key-under-cursor)))))
|
||||
("h" "Copy org heading" (lambda ()
|
||||
(interactive)
|
||||
(kill-new
|
||||
(format "* %s\n\n cite:&%s"
|
||||
(bibtex-completion-apa-format-reference
|
||||
(org-ref-get-bibtex-key-under-cursor))
|
||||
(car (org-ref-get-bibtex-key-and-file))))))]
|
||||
["Edit"
|
||||
("<left>" "Shift left" org-ref-cite-shift-left :transient t)
|
||||
("<right>" "Shift right" org-ref-cite-shift-right :transient t)
|
||||
("<up>" "Sort by year" org-ref-sort-citation-link)
|
||||
("i" "Insert cite" (lambda () (interactive) (funcall org-ref-insert-cite-function)))
|
||||
("t" "Change cite type" org-ref-change-cite-type)
|
||||
("d" "Delete at point" org-ref-delete-citation-at-point)
|
||||
("r" "Replace cite" org-ref-replace-citation-at-point)
|
||||
("P" "Edit pre/suffix" org-ref-edit-pre-post-notes)]
|
||||
["Navigation"
|
||||
("[" "Previous key" org-ref-previous-key :transient t)
|
||||
("]" "Next key" org-ref-next-key :transient t)
|
||||
("v" "Visible key" org-ref-jump-to-visible-key :transient t)
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
|
||||
(defun org-ref-cite-follow (_path)
|
||||
"Follow a cite link."
|
||||
(org-ref-citation-hydra/body))
|
||||
(org-ref-citation-menu))
|
||||
|
||||
(define-obsolete-function-alias 'org-ref-citation-hydra/body
|
||||
#'org-ref-citation-menu "3.1")
|
||||
|
||||
|
||||
;; * Citation links tooltips
|
||||
@@ -637,13 +635,13 @@ Use with apply-partially."
|
||||
(format "[%s]" (cl-second prefix-suffix)))
|
||||
(t
|
||||
""))))
|
||||
(s-format "\\${cmd}${prefix}${suffix}{${keys}}" 'aget
|
||||
(org-ref--format-template "\\${cmd}${prefix}${suffix}{${keys}}"
|
||||
`(("cmd" . ,cmd)
|
||||
("prefix" . ,(string-trim prefix))
|
||||
("suffix" . ,(string-trim suffix))
|
||||
("keys" . ,(string-join keys ","))))))
|
||||
(3
|
||||
(s-format "\\${cmd}${prefix}${suffix}{${keys}}" 'aget
|
||||
(org-ref--format-template "\\${cmd}${prefix}${suffix}{${keys}}"
|
||||
`(("cmd" . ,cmd)
|
||||
;; if there is more than one key, we only do global
|
||||
;; prefix/suffix But for one key, we should allow local
|
||||
@@ -748,7 +746,7 @@ Use with apply-partially."
|
||||
(pcase backend
|
||||
('latex
|
||||
(let ((cite (org-ref-parse-cite-path path)))
|
||||
(s-format "\\${cmd}${global-prefix}${global-suffix}${keys}" 'aget
|
||||
(org-ref--format-template "\\${cmd}${global-prefix}${global-suffix}${keys}"
|
||||
`(("cmd" . ,cmd)
|
||||
("global-prefix" . ,(cond
|
||||
((plist-get cite :prefix)
|
||||
@@ -858,7 +856,7 @@ Use with apply-partially."
|
||||
(setq i (seq-position references key (lambda (el key)
|
||||
(string= key (plist-get el :key)))))
|
||||
;; delete i'th reference
|
||||
(setq references (-remove-at i references))
|
||||
(setq references (org-ref--remove-at i references))
|
||||
(setq data (plist-put data :references references))
|
||||
(save-excursion
|
||||
(goto-char begin)
|
||||
@@ -1006,7 +1004,7 @@ arg COMMON, edit the common prefixes instead."
|
||||
If not on a key, but on a cite, prompt for key."
|
||||
(cond
|
||||
(org-ref-activate-cite-links
|
||||
(if-let ((key (get-text-property (point) 'cite-key)))
|
||||
(if-let* ((key (get-text-property (point) 'cite-key)))
|
||||
;; Point is on a key, so we get it directly
|
||||
key
|
||||
;; point is not on a key, but may still be on a cite link
|
||||
@@ -1046,10 +1044,10 @@ If not on a key, but on a cite, prompt for key."
|
||||
(prog1
|
||||
(get-text-property (point) 'cite-key)
|
||||
(goto-char cp)))))))))
|
||||
|
||||
|
||||
;; org-ref-activate-cite-links is nil so font-lock does not put
|
||||
;; text-properties on keys. We temporarily activate this
|
||||
|
||||
|
||||
(t
|
||||
(let ((el (org-element-context))
|
||||
(org-ref-activate-cite-links t)) ;; temporary
|
||||
@@ -1171,10 +1169,10 @@ If not on a key, but on a cite, prompt for key."
|
||||
Otherwise run `right-word'. If the cursor moves off the link,
|
||||
move to the beginning of the next cite link after this one."
|
||||
(interactive)
|
||||
(when-let (next (next-single-property-change (point) 'cite-key))
|
||||
(when-let* ((next (next-single-property-change (point) 'cite-key)))
|
||||
(goto-char next))
|
||||
(unless (get-text-property (point) 'cite-key)
|
||||
(when-let (next (next-single-property-change (point) 'cite-key))
|
||||
(when-let* ((next (next-single-property-change (point) 'cite-key)))
|
||||
(goto-char next))))
|
||||
|
||||
|
||||
@@ -1184,10 +1182,10 @@ move to the beginning of the next cite link after this one."
|
||||
Otherwise run `left-word'. If the cursor moves off the link,
|
||||
move to the beginning of the previous cite link after this one."
|
||||
(interactive)
|
||||
(when-let (prev (previous-single-property-change (point) 'cite-key))
|
||||
(when-let* ((prev (previous-single-property-change (point) 'cite-key)))
|
||||
(goto-char prev))
|
||||
(unless (get-text-property (point) 'cite-key)
|
||||
(when-let (prev (previous-single-property-change (point) 'cite-key))
|
||||
(when-let* ((prev (previous-single-property-change (point) 'cite-key)))
|
||||
(goto-char prev))))
|
||||
|
||||
(defvar avy-goto-key)
|
||||
@@ -1316,7 +1314,7 @@ Rules:
|
||||
(string= key-at-point (plist-get el1 :key)))))
|
||||
|
||||
(setq data (plist-put data :references
|
||||
(-insert-at
|
||||
(org-ref--insert-at
|
||||
(+ index (if (and (= 3 version) (looking-at "&"))
|
||||
0
|
||||
1))
|
||||
|
||||
@@ -35,13 +35,11 @@
|
||||
|
||||
(require 'org-element)
|
||||
|
||||
(require 'dash)
|
||||
(require 'f)
|
||||
(require 's)
|
||||
(require 'org-ref-utils)
|
||||
|
||||
(require 'parsebib)
|
||||
(require 'bibtex-completion)
|
||||
(require 'hydra)
|
||||
(require 'transient)
|
||||
|
||||
(require 'org-ref-bibliography-links)
|
||||
(require 'org-ref-citation-links)
|
||||
@@ -172,9 +170,7 @@ set in `bibtex-completion-bibliography'"
|
||||
(throw 'result (nreverse (delete-dups (mapcar 'org-ref-get-bibfile-path org-ref-bibliography-files)))))))
|
||||
;; we did not find anything. use defaults. Make sure we have a list in
|
||||
;; case it is a single string.
|
||||
(throw 'result (if (listp bibtex-completion-bibliography)
|
||||
bibtex-completion-bibliography
|
||||
(list bibtex-completion-bibliography)))))))
|
||||
(throw 'result (org-ref-normalize-bibtex-completion-bibliography))))))
|
||||
|
||||
|
||||
(defun org-ref-key-in-file-p (key filename)
|
||||
@@ -188,18 +184,16 @@ set in `bibtex-completion-bibliography'"
|
||||
|
||||
(defun org-ref-possible-bibfiles ()
|
||||
"Make a unique list of possible bibliography files for completing-read"
|
||||
(-uniq
|
||||
(delete-dups
|
||||
(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)
|
||||
(org-ref--directory-files "." (lambda (f)
|
||||
(and (not (string-match "#" f))
|
||||
(f-ext? f "bib"))))
|
||||
(org-ref--file-ext-p f "bib"))))
|
||||
;; and last in the default bibliography
|
||||
(if (stringp bibtex-completion-bibliography)
|
||||
(list bibtex-completion-bibliography)
|
||||
bibtex-completion-bibliography))))
|
||||
(org-ref-normalize-bibtex-completion-bibliography))))
|
||||
|
||||
|
||||
(defun org-ref-get-bibtex-key-and-file (&optional key)
|
||||
@@ -236,65 +230,106 @@ provide their own version."
|
||||
|
||||
|
||||
;; This is an alternative that doesn't rely on prefix args.
|
||||
(defhydra org-ref-insert-link-hydra (:color red :hint nil)
|
||||
"Insert an org-ref link
|
||||
"
|
||||
("[" (funcall org-ref-insert-cite-function) "Citation" :column "org-ref")
|
||||
("]" (funcall org-ref-insert-ref-function) "Cross-reference" :column "org-ref")
|
||||
("\\" (funcall org-ref-insert-label-function) "Label" :column "org-ref")
|
||||
|
||||
("bs" (insert (org-ref-bibliographystyle-complete-link)) "Bibliographystyle" :column "Bibliography" :color blue)
|
||||
("bf" (insert (org-ref-bibliography-complete)) "Bibliography" :column "Bibliography" :color blue)
|
||||
("nb" (insert (org-ref-nobibliography-complete)) "Bibliography" :column "Bibliography" :color blue)
|
||||
|
||||
("g" org-ref-insert-glossary-link "Glossary link" :column "Glossary" :color blue)
|
||||
("a" org-ref-insert-acronym-link "Acronym link" :column "Glossary" :color blue)
|
||||
("ng" (progn
|
||||
(org-mark-ring-push)
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward "#\\+name: glossary" nil t)
|
||||
(progn
|
||||
(goto-char (org-element-property :contents-end (org-element-context)))
|
||||
(backward-char)
|
||||
(org-table-insert-row '(4)))
|
||||
;; no table found
|
||||
(goto-char (point-max))
|
||||
(insert "\n\n#+name: glossary
|
||||
(defun org-ref-insert-link-menu--insert-citation ()
|
||||
(interactive)
|
||||
(funcall org-ref-insert-cite-function))
|
||||
|
||||
(defun org-ref-insert-link-menu--insert-reference ()
|
||||
(interactive)
|
||||
(funcall org-ref-insert-ref-function))
|
||||
|
||||
(defun org-ref-insert-link-menu--insert-label ()
|
||||
(interactive)
|
||||
(funcall org-ref-insert-label-function))
|
||||
|
||||
(defun org-ref-insert-link-menu--open-bibliography ()
|
||||
(interactive)
|
||||
(find-file (completing-read "Bibliography: " (org-ref-find-bibliography))))
|
||||
|
||||
(defun org-ref-insert-link-menu--ensure-table (name template)
|
||||
(org-mark-ring-push)
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward (format "#\\+name: %s" name) nil t)
|
||||
(progn
|
||||
(goto-char (org-element-property :contents-end (org-element-context)))
|
||||
(backward-char)
|
||||
(org-table-insert-row '(4)))
|
||||
(goto-char (point-max))
|
||||
(insert template)
|
||||
(beginning-of-line)
|
||||
(forward-char)))
|
||||
|
||||
(defun org-ref-insert-link-menu--new-glossary-term ()
|
||||
(interactive)
|
||||
(org-ref-insert-link-menu--ensure-table
|
||||
"glossary"
|
||||
"\n\n#+name: glossary
|
||||
| label | term | definition |
|
||||
|-------+---------+-------------------------------|
|
||||
| | | |")
|
||||
(beginning-of-line)
|
||||
(forward-char)))
|
||||
"New glossary term" :column "Glossary")
|
||||
|
||||
("na" (progn
|
||||
(org-mark-ring-push)
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward "#\\+name: acronym" nil t)
|
||||
(progn
|
||||
(goto-char (org-element-property :contents-end (org-element-context)))
|
||||
(backward-char)
|
||||
(org-table-insert-row '(4)))
|
||||
;; no table found
|
||||
(goto-char (point-max))
|
||||
(insert "\n\n#+name: acronyms
|
||||
| | | |"))
|
||||
|
||||
(defun org-ref-insert-link-menu--new-acronym-term ()
|
||||
(interactive)
|
||||
(org-ref-insert-link-menu--ensure-table
|
||||
"acronym"
|
||||
"\n\n#+name: acronyms
|
||||
| label | abbreviation | full form |
|
||||
|-------+--------------+----------------------------|
|
||||
| | | |")
|
||||
(beginning-of-line)
|
||||
(forward-char)))
|
||||
"New acronym term" :column "Glossary")
|
||||
| | | |"))
|
||||
|
||||
("bd" doi-add-bibtex-entry "Add bibtex entry from a DOI" :column "Bibtex")
|
||||
("bc" crossref-add-bibtex-entry "Add bibtex entry from Crossref" :column "Bibtex")
|
||||
("bo" (find-file (completing-read "Bibliography: " (org-ref-find-bibliography)))
|
||||
"Open bibtex file" :column "Bibtex")
|
||||
|
||||
("t" (insert "[[list-of-tables:]]\n") "List of tables" :column "Misc")
|
||||
("f" (insert "[[list-of-figures:]]\n") "List of figures" :column "Misc")
|
||||
("i" (insert (format "[[index:%s]]" (string-trim (read-string "Index entry: ")))) "Index entry" :column "Misc")
|
||||
("pi" (insert "[[printindex:]]") "Print index" :column "Misc")
|
||||
("pg" (insert "[[printglossaries:]]") "Print glossary" :column "Misc"))
|
||||
(defun org-ref-insert-link-menu--insert-string (string)
|
||||
(insert string))
|
||||
|
||||
(defun org-ref-insert-link-menu--insert-index (prompt template)
|
||||
(org-ref-insert-link-menu--insert-string
|
||||
(format template (string-trim (read-string prompt)))))
|
||||
|
||||
(transient-define-prefix org-ref-insert-link-menu ()
|
||||
"Insert an org-ref link."
|
||||
[["org-ref"
|
||||
("]" "Citation" org-ref-insert-link-menu--insert-citation :transient t)
|
||||
("r" "Cross-reference" org-ref-insert-link-menu--insert-reference :transient t)
|
||||
("\\" "Label" org-ref-insert-link-menu--insert-label :transient t)]
|
||||
["Bibliography"
|
||||
("bs" "Bibliographystyle" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string
|
||||
(org-ref-bibliographystyle-complete-link))))
|
||||
("bf" "Bibliography" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string
|
||||
(org-ref-bibliography-complete))))
|
||||
("nb" "Nobibliography" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string
|
||||
(org-ref-nobibliography-complete))))]
|
||||
["Glossary"
|
||||
("g" "Glossary link" org-ref-insert-glossary-link)
|
||||
("a" "Acronym link" org-ref-insert-acronym-link)
|
||||
("ng" "New glossary term" org-ref-insert-link-menu--new-glossary-term :transient t)
|
||||
("na" "New acronym term" org-ref-insert-link-menu--new-acronym-term :transient t)]
|
||||
["Bibtex"
|
||||
("bd" "Add bibtex entry from a DOI" doi-add-bibtex-entry :transient t)
|
||||
("bc" "Add bibtex entry from Crossref" crossref-add-bibtex-entry :transient t)
|
||||
("bo" "Open bibtex file" org-ref-insert-link-menu--open-bibliography :transient t)]
|
||||
["Misc"
|
||||
("t" "List of tables" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string "[[list-of-tables:]]\n"))
|
||||
:transient t)
|
||||
("f" "List of figures" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string "[[list-of-figures:]]\n"))
|
||||
:transient t)
|
||||
("i" "Index entry" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-index
|
||||
"Index entry: " "[[index:%s]]"))
|
||||
:transient t)
|
||||
("pi" "Print index" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string "[[printindex:]]"))
|
||||
:transient t)
|
||||
("pg" "Print glossary" (lambda () (interactive)
|
||||
(org-ref-insert-link-menu--insert-string "[[printglossaries:]]"))
|
||||
:transient t)
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
(define-obsolete-function-alias 'org-ref-insert-link-hydra/body
|
||||
#'org-ref-insert-link-menu "3.1")
|
||||
|
||||
|
||||
;;* org-ref-help
|
||||
|
||||
@@ -39,18 +39,36 @@
|
||||
;; export is your goal, you may want to use org-ref-refproc.el to handle
|
||||
;; cross-references.
|
||||
;;
|
||||
;;; Formatting Limitations:
|
||||
;;
|
||||
;; Different export backends have different formatting capabilities. HTML and
|
||||
;; LaTeX support full CSL formatting including small caps, italics, bold, etc.
|
||||
;;
|
||||
;; The org-mode backend has limitations because org-mode syntax does not support
|
||||
;; small caps. Many CSL citation styles use small caps for author names, so these
|
||||
;; will not render correctly when exporting to formats that use the org backend
|
||||
;; (like DOCX by default).
|
||||
;;
|
||||
;; For DOCX export with better formatting support, set
|
||||
;; `org-ref-docx-use-html-backend' to t. This causes citations to be rendered
|
||||
;; as HTML which pandoc can convert to DOCX while preserving formatting.
|
||||
;;
|
||||
;; See `org-ref-backend-csl-formats' to customize backend mappings for different
|
||||
;; export formats.
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
(eval-when-compile
|
||||
(require 'hydra))
|
||||
(require 'transient)
|
||||
(require 'org-ref-utils)
|
||||
(require 'org-ref-citation-links)
|
||||
|
||||
(defvar hfy-user-sheet-assoc) ; to quiet compiler
|
||||
|
||||
(require 'ox-org)
|
||||
(if (executable-find "pandoc")
|
||||
(require 'ox-pandoc))
|
||||
(when (executable-find "pandoc")
|
||||
(ignore-errors (require 'ox-pandoc)))
|
||||
|
||||
(require 'citeproc)
|
||||
(ignore-errors (require 'citeproc))
|
||||
|
||||
(defvar org-cite-csl-styles-dir)
|
||||
|
||||
@@ -62,7 +80,24 @@
|
||||
(ascii . plain)
|
||||
(odt . org-odt)
|
||||
(docx . org))
|
||||
"Mapping of export backend to csl-backends."
|
||||
"Mapping of export backend to csl-backends.
|
||||
|
||||
Each entry maps an org export backend symbol to a citeproc-el backend
|
||||
format used for rendering citations and bibliographies.
|
||||
|
||||
Note: The 'org backend has formatting limitations because org-mode
|
||||
syntax does not support small caps. CSL styles that use small caps
|
||||
(common for author names) will not render correctly when using the
|
||||
'org backend. For DOCX export, consider setting
|
||||
`org-ref-docx-use-html-backend' to t to use HTML formatting which
|
||||
pandoc can convert while preserving small caps and other formatting.
|
||||
|
||||
Available citeproc backends:
|
||||
- html: Full HTML formatting with CSS styles
|
||||
- latex: Full LaTeX formatting with commands like \\textsc{}
|
||||
- org: Org-mode markup (limited, no small caps support)
|
||||
- org-odt: ODT-specific XML formatting
|
||||
- plain: Plain text with no formatting"
|
||||
:type '(alist :key-type (symbol) :value-type (symbol))
|
||||
:group 'org-ref)
|
||||
|
||||
@@ -92,6 +127,30 @@ Should be a csl filename, or an absolute path to a csl filename."
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defcustom org-ref-docx-use-html-backend nil
|
||||
"Use HTML backend for DOCX export to preserve CSL formatting.
|
||||
|
||||
When non-nil, citations exported to DOCX will be rendered using the
|
||||
HTML backend, which preserves formatting like small caps and italics
|
||||
that pandoc can convert to DOCX.
|
||||
|
||||
When nil (default), uses the org backend which has limited formatting
|
||||
support due to org-mode syntax limitations. Specifically, org-mode has
|
||||
no native syntax for small caps, so CSL styles that use small caps
|
||||
(common for author names in many citation styles) will not render
|
||||
correctly in DOCX output.
|
||||
|
||||
Note: This option only affects DOCX export via pandoc. Other export
|
||||
formats (HTML, LaTeX, ODT) are not affected.
|
||||
|
||||
See also `org-ref-backend-csl-formats' to customize backend mappings
|
||||
for other export formats.
|
||||
|
||||
Related to issue #981: https://github.com/jkitchin/org-ref/issues/981"
|
||||
:type 'boolean
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defcustom org-ref-csl-label-aliases
|
||||
'((("app" "apps") . "appendix")
|
||||
(("art" "arts") . "article-locator")
|
||||
@@ -128,7 +187,7 @@ See https://github.com/citation-style-language/documentation/blob/master/specifi
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defcustom org-ref-export-suppress-affix-types
|
||||
(defcustom org-ref-export-suppress-affix-types
|
||||
'("citet"
|
||||
"citet*"
|
||||
"citetitle"
|
||||
@@ -141,6 +200,68 @@ See https://github.com/citation-style-language/documentation/blob/master/specifi
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
;;; Footnote citation support (issue #993)
|
||||
|
||||
(defvar org-ref-footnote-counter 0
|
||||
"Counter for footnote citations during export.
|
||||
Reset to 0 at the start of each export process.")
|
||||
|
||||
(defcustom org-ref-footnote-cite-types
|
||||
'("footcite"
|
||||
"footfullcite"
|
||||
"footcitetext"
|
||||
"footcites"
|
||||
"footcitetexts"
|
||||
"smartcite"
|
||||
"Smartcite")
|
||||
"List of citation types that should be rendered as footnotes.
|
||||
These citation types will be given a :note-index parameter when
|
||||
passed to citeproc-el, which causes them to be rendered as notes/footnotes
|
||||
when using CSL styles that support note formatting."
|
||||
:type '(repeat string)
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defun org-ref-footnote-cite-type-p (cite-type)
|
||||
"Return non-nil if CITE-TYPE should be rendered as a footnote.
|
||||
CITE-TYPE is a string like \"footcite\" or \"cite\"."
|
||||
(member cite-type org-ref-footnote-cite-types))
|
||||
|
||||
|
||||
(defun org-ref-get-next-footnote-number ()
|
||||
"Get the next footnote number and increment the counter.
|
||||
Returns the next sequential footnote number (1, 2, 3, ...)."
|
||||
(setq org-ref-footnote-counter (1+ org-ref-footnote-counter)))
|
||||
|
||||
|
||||
(defun org-ref-csl-style-supports-notes-p (csl-style-file)
|
||||
"Return non-nil if CSL-STYLE-FILE supports note/footnote formatting.
|
||||
CSL-STYLE-FILE is a filename like \"chicago-fullnote-bibliography.csl\".
|
||||
|
||||
This function checks if the CSL style file contains note-related
|
||||
class attributes, which indicate it supports footnote formatting."
|
||||
(or
|
||||
;; Check filename for common note-style indicators
|
||||
;; Match: -note-, -note.csl, fullnote, footnote
|
||||
(string-match-p "\\(\\bnote\\b\\|fullnote\\|footnote\\)" csl-style-file)
|
||||
;; If we have the actual file, check its contents
|
||||
(when (file-exists-p csl-style-file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents csl-style-file)
|
||||
(goto-char (point-min))
|
||||
;; Look for class="note" in the CSL XML
|
||||
(re-search-forward "class=\"note\"" nil t)))))
|
||||
|
||||
|
||||
(defun org-ref-get-citation-note-index (citation-link)
|
||||
"Get the note-index for CITATION-LINK if it's a footnote citation type.
|
||||
CITATION-LINK is an org-element link object.
|
||||
Returns a footnote number if this is a footnote citation, nil otherwise."
|
||||
(when (org-ref-footnote-cite-type-p
|
||||
(org-element-property :type citation-link))
|
||||
(org-ref-get-next-footnote-number)))
|
||||
|
||||
|
||||
(defun org-ref-dealias-label (alias)
|
||||
"Return the full, de-aliased label for ALIAS.
|
||||
Looked up from `org-ref-csl-label-aliases'.
|
||||
@@ -234,10 +355,17 @@ REF is a plist data structure returned from `org-ref-parse-cite-path'."
|
||||
"Process the citations and bibliography in the org-buffer.
|
||||
Usually run on a copy of the buffer during export.
|
||||
BACKEND is the org export backend."
|
||||
;; Reset footnote counter for this export
|
||||
(setq org-ref-footnote-counter 0)
|
||||
|
||||
(save-restriction
|
||||
(when subtreep
|
||||
(org-narrow-to-subtree))
|
||||
(let* ((csl-backend (or (cdr (assoc backend org-ref-backend-csl-formats)) 'plain))
|
||||
;; Use HTML backend for DOCX if requested for better formatting
|
||||
(csl-backend (if (and (eq backend 'docx) org-ref-docx-use-html-backend)
|
||||
'html
|
||||
csl-backend))
|
||||
|
||||
(style (or (cadr (assoc "CSL-STYLE"
|
||||
(org-collect-keywords
|
||||
@@ -248,6 +376,24 @@ BACKEND is the org export backend."
|
||||
'("CSL-LOCALE"))))
|
||||
org-ref-csl-default-locale))
|
||||
|
||||
;; Determine the actual style file path for checking note support
|
||||
(style-file (cond
|
||||
((file-exists-p style)
|
||||
style)
|
||||
((and (bound-and-true-p org-cite-csl-styles-dir)
|
||||
(file-exists-p (org-ref--file-join org-cite-csl-styles-dir style)))
|
||||
(org-ref--file-join org-cite-csl-styles-dir style))
|
||||
((file-exists-p (expand-file-name style
|
||||
(org-ref--file-join (file-name-directory
|
||||
(locate-library "org-ref"))
|
||||
"citeproc/csl-styles")))
|
||||
(expand-file-name style (org-ref--file-join
|
||||
(file-name-directory
|
||||
(locate-library "org-ref"))
|
||||
"citeproc/csl-styles")))
|
||||
(t
|
||||
style))) ; Fallback to style name itself
|
||||
|
||||
(proc (citeproc-create
|
||||
;; The style
|
||||
(cond
|
||||
@@ -255,14 +401,14 @@ BACKEND is the org export backend."
|
||||
style)
|
||||
;; In a user-dir
|
||||
((and (bound-and-true-p org-cite-csl-styles-dir)
|
||||
(file-exists-p (f-join org-cite-csl-styles-dir style)))
|
||||
(f-join org-cite-csl-styles-dir style))
|
||||
(file-exists-p (org-ref--file-join org-cite-csl-styles-dir style)))
|
||||
(org-ref--file-join org-cite-csl-styles-dir style))
|
||||
;; provided by org-ref
|
||||
((file-exists-p (expand-file-name style
|
||||
(f-join (file-name-directory
|
||||
(org-ref--file-join (file-name-directory
|
||||
(locate-library "org-ref"))
|
||||
"citeproc/csl-styles")))
|
||||
(expand-file-name style (f-join
|
||||
(expand-file-name style (org-ref--file-join
|
||||
(file-name-directory
|
||||
(locate-library "org-ref"))
|
||||
"citeproc/csl-styles")))
|
||||
@@ -274,7 +420,7 @@ BACKEND is the org export backend."
|
||||
;; locale getter
|
||||
(citeproc-locale-getter-from-dir (if (bound-and-true-p org-cite-csl-locales-dir)
|
||||
org-cite-csl-locales-dir
|
||||
(f-join (file-name-directory
|
||||
(org-ref--file-join (file-name-directory
|
||||
(locate-library "org-ref"))
|
||||
"citeproc/csl-locales")))
|
||||
;; the actual locale
|
||||
@@ -338,8 +484,15 @@ BACKEND is the org export backend."
|
||||
"[A-Z]"
|
||||
(substring
|
||||
(org-element-property :type cl) 0 1))
|
||||
;; I don't know where this information would come from.
|
||||
:note-index nil
|
||||
;; Set note-index for footnote citation types (issue #993)
|
||||
:note-index (when (org-ref-footnote-cite-type-p
|
||||
(org-element-property :type cl))
|
||||
;; Warn if using footnote citations with non-note style
|
||||
(unless (org-ref-csl-style-supports-notes-p style-file)
|
||||
(warn "Citation type '%s' is a footnote citation but CSL style '%s' may not support notes. Consider using a note-based style like chicago-fullnote-bibliography.csl"
|
||||
(org-element-property :type cl)
|
||||
style))
|
||||
(org-ref-get-next-footnote-number))
|
||||
:ignore-et-al nil
|
||||
:grouped nil))))
|
||||
|
||||
@@ -547,6 +700,11 @@ VISIBLE-ONLY BODY-ONLY and INFO."
|
||||
(defun org-ref-export-to-docx (&optional async subtreep visible-only
|
||||
body-only info)
|
||||
"Export the buffer to docx via pandoc and open.
|
||||
|
||||
Note: By default, DOCX export uses the org backend for citations which
|
||||
has formatting limitations (no small caps support). Set
|
||||
`org-ref-docx-use-html-backend' to t for better formatting preservation.
|
||||
|
||||
See `org-export-as' for the meaning of ASYNC SUBTREEP
|
||||
VISIBLE-ONLY BODY-ONLY and INFO."
|
||||
(org-ref-export-to 'docx async subtreep visible-only
|
||||
@@ -614,23 +772,67 @@ I am not positive on this though."
|
||||
(org-at-heading-p)))))
|
||||
|
||||
|
||||
;; A hydra exporter with preprocessors
|
||||
(defhydradio org-ref ()
|
||||
(natmove "natmove")
|
||||
(citeproc "CSL citations")
|
||||
(refproc "cross-references")
|
||||
(acrossproc "Acronyms, glossary")
|
||||
(idxproc "Index")
|
||||
(bblproc "BBL citations"))
|
||||
;; A transient exporter with preprocessors
|
||||
(defvar org-ref/natmove nil
|
||||
"Toggle for moving natbib citations before export.")
|
||||
|
||||
(defvar org-ref/citeproc nil
|
||||
"Toggle for running citeproc before export.")
|
||||
|
||||
(defvar org-ref/refproc nil
|
||||
"Toggle for running refproc before export.")
|
||||
|
||||
(defvar org-ref/acrossproc nil
|
||||
"Toggle for running acrossproc before export.")
|
||||
|
||||
(defvar org-ref/idxproc nil
|
||||
"Toggle for running idxproc before export.")
|
||||
|
||||
(defvar org-ref/bblproc nil
|
||||
"Toggle for running bblproc before export.")
|
||||
|
||||
(defun org-ref-export--toggle (var)
|
||||
(set var (not (symbol-value var)))
|
||||
(message "%s %s"
|
||||
(capitalize (replace-regexp-in-string "/" " " (symbol-name var)))
|
||||
(if (symbol-value var) "enabled" "disabled"))
|
||||
(symbol-value var))
|
||||
|
||||
(defun org-ref-export-toggle-natmove ()
|
||||
(interactive)
|
||||
(org-ref-export--toggle 'org-ref/natmove))
|
||||
|
||||
(defun org-ref-export-toggle-citeproc ()
|
||||
(interactive)
|
||||
(org-ref-export--toggle 'org-ref/citeproc))
|
||||
|
||||
(defun org-ref-export-toggle-refproc ()
|
||||
(interactive)
|
||||
(org-ref-export--toggle 'org-ref/refproc))
|
||||
|
||||
(defun org-ref-export-toggle-acrossproc ()
|
||||
(interactive)
|
||||
(org-ref-export--toggle 'org-ref/acrossproc))
|
||||
|
||||
(defun org-ref-export-toggle-idxproc ()
|
||||
(interactive)
|
||||
(org-ref-export--toggle 'org-ref/idxproc))
|
||||
|
||||
(defun org-ref-export-toggle-bblproc ()
|
||||
(interactive)
|
||||
(org-ref-export--toggle 'org-ref/bblproc))
|
||||
|
||||
(defun org-ref-export--toggle-description (label var)
|
||||
(format "%s %s" label (if (symbol-value var) "[on]" "[off]")))
|
||||
|
||||
|
||||
(defun org-ref-export-from-hydra (&optional arg)
|
||||
"Run the export dispatcher with the desired hooks selected in `org-ref-export/body'."
|
||||
(defun org-ref-export-dispatch (&optional arg)
|
||||
"Run the export dispatcher with the desired hooks selected in the export menu."
|
||||
(interactive "P")
|
||||
|
||||
(when (and org-ref/citeproc org-ref/bblproc)
|
||||
(error "You cannot use CSL and BBL at the same time."))
|
||||
|
||||
|
||||
(let ((org-export-before-parsing-functions org-export-before-parsing-functions))
|
||||
(when org-ref/citeproc
|
||||
(cl-pushnew 'org-ref-csl-preprocess-buffer org-export-before-parsing-functions))
|
||||
@@ -648,28 +850,37 @@ I am not positive on this though."
|
||||
(unless (featurep 'org-ref-natbib-bbl-citeproc)
|
||||
(require 'org-ref-natbib-bbl-citeproc))
|
||||
(cl-pushnew 'org-ref-bbl-preprocess org-export-before-parsing-functions))
|
||||
|
||||
|
||||
;; this goes last since it moves cites before they might get replaced.
|
||||
(when org-ref/natmove
|
||||
(cl-pushnew 'org-ref-cite-natmove org-export-before-parsing-functions))
|
||||
|
||||
(org-export-dispatch arg)))
|
||||
|
||||
(transient-define-prefix org-ref-export-menu ()
|
||||
"Select export preprocessors and dispatch export."
|
||||
[["Preprocessors"
|
||||
("n" org-ref-export-toggle-natmove :transient t
|
||||
:description (lambda () (org-ref-export--toggle-description "natmove" 'org-ref/natmove)))
|
||||
("c" org-ref-export-toggle-citeproc :transient t
|
||||
:description (lambda () (org-ref-export--toggle-description "citeproc" 'org-ref/citeproc)))
|
||||
("r" org-ref-export-toggle-refproc :transient t
|
||||
:description (lambda () (org-ref-export--toggle-description "refproc" 'org-ref/refproc)))
|
||||
("a" org-ref-export-toggle-acrossproc :transient t
|
||||
:description (lambda () (org-ref-export--toggle-description "acrossproc" 'org-ref/acrossproc)))
|
||||
("i" org-ref-export-toggle-idxproc :transient t
|
||||
:description (lambda () (org-ref-export--toggle-description "idxproc" 'org-ref/idxproc)))
|
||||
("b" org-ref-export-toggle-bblproc :transient t
|
||||
:description (lambda () (org-ref-export--toggle-description "bblproc" 'org-ref/bblproc)))]
|
||||
["Actions"
|
||||
("e" "Export" org-ref-export-dispatch)
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
(defhydra org-ref-export (:color red)
|
||||
"
|
||||
_C-n_: natmove % -15`org-ref/natmove _C-c_: citeproc % -15`org-ref/citeproc^^^ _C-r_: refproc % -15`org-ref/refproc^^^
|
||||
_C-a_: acrossproc % -15`org-ref/acrossproc _C-i_: idxproc % -15`org-ref/idxproc^^^ _C-b_: bblproc % -15`org-ref/bblproc^^^
|
||||
"
|
||||
("C-n" (org-ref/natmove) nil)
|
||||
("C-c" (org-ref/citeproc) nil)
|
||||
("C-r" (org-ref/refproc) nil)
|
||||
("C-a" (org-ref/acrossproc) nil)
|
||||
("C-i" (org-ref/idxproc) nil)
|
||||
("C-b" (org-ref/bblproc) nil)
|
||||
(define-obsolete-function-alias 'org-ref-export/body
|
||||
#'org-ref-export-menu "3.1")
|
||||
|
||||
("e" org-ref-export-from-hydra "Export" :color blue)
|
||||
("q" nil "quit"))
|
||||
(define-obsolete-function-alias 'org-ref-export-from-hydra
|
||||
#'org-ref-export-dispatch "3.1")
|
||||
|
||||
(provide 'org-ref-export)
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
(declare-function org-ref-clean-bibtex-entry "org-ref-bibtex" ())
|
||||
(declare-function xml-substitute-special "xml" ())
|
||||
|
||||
|
||||
(defun org-ref--extract (html-buffer rx num)
|
||||
"Return content matched within HTML-BUFFER by RX at parenthesized
|
||||
@@ -104,9 +109,12 @@ and return the buffer."
|
||||
(interactive "MOpenReview ID: ")
|
||||
(let* ((url (concat "https://openreview.net/forum?id=" id))
|
||||
(html-buffer (org-ref--html-buffer url)))
|
||||
(with-current-buffer html-buffer
|
||||
(replace-string-in-region "\\\\n" "\\n" (point-min) (point-max)))
|
||||
(org-ref--extract-entry-from-html
|
||||
html-buffer
|
||||
'("\"_bibtex\":\"\\(@.+?}\\)\"" . 1)
|
||||
'("\\\\\"_bibtex\\\\\":\\({\\\\\"value\\\\\":\\)?\\\\\"\\(@.+?}\\)\\\\\""
|
||||
. 2)
|
||||
(replace-regexp-in-string "forum" "pdf" url)
|
||||
'("abstract" .
|
||||
("<meta name=\"citation_abstract\" content=\"\\(.+?\\(\n.*?\\)*?\\)\"/>" . 1))
|
||||
@@ -117,7 +125,7 @@ and return the buffer."
|
||||
("\\(Summary\\|TL;DR\\).*?\"note-content-value\">\\(.+?\\)</span>" . 2))
|
||||
;; Should we proactively download supplementary materials too?
|
||||
(cons "supp"
|
||||
(if-let ((supp (org-ref--extract
|
||||
(if-let* ((supp (org-ref--extract
|
||||
html-buffer
|
||||
">Supplementary Material<.*?href=\"\\([^\"]+\\)" 1)))
|
||||
(concat "https://openreview.net" supp))))))
|
||||
@@ -160,7 +168,7 @@ and return the buffer."
|
||||
'("abstract" . ("<h4>Abstract</h4>[ \n]*?\\(<p>\\)+\\(.+?\\)</p>" . 2))
|
||||
;; Should we proactively download supplementary materials too?
|
||||
(cons "supp"
|
||||
(if-let
|
||||
(if-let*
|
||||
((supp (org-ref--extract
|
||||
html-buffer
|
||||
"href=[\"']\\([^\"']+-Supplemental[^\"']*\\)[\"']" 1)))
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
(require 'org-element)
|
||||
(require 'org-ref-utils)
|
||||
(require 'ox)
|
||||
(require 'org-ref-ref-links) ; For multi-file support utilities
|
||||
|
||||
;;; Code:
|
||||
(defgroup org-ref-glossary nil
|
||||
@@ -102,6 +103,28 @@ This is not always fast, so we provide a way to disable it."
|
||||
:group 'org-ref-glossary)
|
||||
|
||||
|
||||
(defcustom org-ref-glossary-show-tooltips t
|
||||
"If non-nil, show tooltips when hovering over glossary and acronym links.
|
||||
When nil, tooltips are disabled entirely for glossary links, which can
|
||||
improve responsiveness if you find the tooltips distracting or slow.
|
||||
|
||||
This is separate from `org-ref-activate-glossary-links' which controls
|
||||
whether links are fontified and clickable."
|
||||
:type 'boolean
|
||||
:group 'org-ref-glossary)
|
||||
|
||||
|
||||
(defcustom org-ref-glossary-enable-multi-file t
|
||||
"Enable scanning #+INCLUDE'd files for glossary/acronym definitions.
|
||||
When non-nil, glossary and acronym lookups will search in files included
|
||||
via #+INCLUDE directives, enabling multi-file document support.
|
||||
|
||||
Uses timestamp-based caching to maintain performance. Only files that have
|
||||
changed since the last scan are re-parsed."
|
||||
:type 'boolean
|
||||
:group 'org-ref-glossary)
|
||||
|
||||
|
||||
(defcustom org-ref-glsentries '()
|
||||
"Variable to hold locations of glsentries load files.")
|
||||
|
||||
@@ -114,6 +137,18 @@ This is not always fast, so we provide a way to disable it."
|
||||
"Buffer-local variable for acronym entry cache.")
|
||||
|
||||
|
||||
(defvar org-ref-glossary-file-cache (make-hash-table :test 'equal)
|
||||
"Global cache of glossary entries per file.
|
||||
Maps file paths to lists of glossary entry plists.
|
||||
Used when `org-ref-glossary-enable-multi-file' is non-nil.")
|
||||
|
||||
|
||||
(defvar org-ref-acronym-file-cache (make-hash-table :test 'equal)
|
||||
"Global cache of acronym entries per file.
|
||||
Maps file paths to lists of acronym entry plists.
|
||||
Used when `org-ref-glossary-enable-multi-file' is non-nil.")
|
||||
|
||||
|
||||
(defun or-find-closing-curly-bracket (&optional limit)
|
||||
"Find closing bracket for the bracket at point and move point to it.
|
||||
Go up to LIMIT or `point-max'. This is a parsing function. I
|
||||
@@ -160,12 +195,12 @@ changes."
|
||||
(let* (end-of-entry
|
||||
data
|
||||
(external (when (re-search-forward
|
||||
"\\loadglsentries\\(\\[.*\\]\\){\\(?1:.*\\)}" nil t)
|
||||
"\\\\loadglsentries\\(\\[.*\\]\\)?{\\(?1:.*\\)}" nil t)
|
||||
(match-string 1)))
|
||||
(glsentries (and external
|
||||
(or (cdr (assoc external org-ref-glsentries))
|
||||
(progn
|
||||
(cl-pushnew (cons external (s-trim
|
||||
(cl-pushnew (cons external (string-trim
|
||||
(shell-command-to-string
|
||||
(format "kpsewhich tex %s"
|
||||
external))))
|
||||
@@ -268,6 +303,131 @@ changes."
|
||||
data))))
|
||||
|
||||
|
||||
;;** Multi-file glossary support
|
||||
|
||||
(defun or-scan-file-for-glossary-table (file)
|
||||
"Scan FILE and return list of glossary entries from #+name: glossary table.
|
||||
Returns list of plists with :label, :name, :description, :file.
|
||||
Returns nil if no glossary table is found in FILE."
|
||||
(when (file-exists-p file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-mode)
|
||||
(let ((entries '()))
|
||||
(or (catch 'found
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'table
|
||||
(lambda (el)
|
||||
(when (string= "glossary" (org-element-property :name el))
|
||||
(goto-char (org-element-property :contents-begin el))
|
||||
(let ((table-data (nthcdr 2 (org-babel-read-table))))
|
||||
;; Convert table rows to plists
|
||||
(dolist (row table-data)
|
||||
(when (and (listp row) (= 3 (length row)))
|
||||
(push (list :label (nth 0 row)
|
||||
:name (nth 1 row)
|
||||
:description (nth 2 row)
|
||||
:file file)
|
||||
entries)))
|
||||
(throw 'found (nreverse entries)))))))
|
||||
entries)))))
|
||||
|
||||
|
||||
(defun or-scan-file-for-acronym-table (file)
|
||||
"Scan FILE and return list of acronym entries from #+name: acronyms table.
|
||||
Returns list of plists with :label, :abbrv, :full, :file.
|
||||
Returns nil if no acronym table is found in FILE."
|
||||
(when (file-exists-p file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-mode)
|
||||
(let ((entries '()))
|
||||
(or (catch 'found
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'table
|
||||
(lambda (el)
|
||||
(when (string= "acronyms" (org-element-property :name el))
|
||||
(goto-char (org-element-property :contents-begin el))
|
||||
(let ((table-data (nthcdr 2 (org-babel-read-table))))
|
||||
;; Convert table rows to plists
|
||||
(dolist (row table-data)
|
||||
(when (and (listp row) (= 3 (length row)))
|
||||
(push (list :label (nth 0 row)
|
||||
:abbrv (nth 1 row)
|
||||
:full (nth 2 row)
|
||||
:file file)
|
||||
entries)))
|
||||
(throw 'found (nreverse entries)))))))
|
||||
entries)))))
|
||||
|
||||
|
||||
(defun or-parse-glossary-entry-multi-file (label)
|
||||
"Find glossary LABEL in current file or included files.
|
||||
Uses timestamp-based caching to avoid re-scanning unchanged files.
|
||||
Returns plist with :label, :name, :description, :file."
|
||||
(when (and (boundp 'org-ref-glossary-enable-multi-file)
|
||||
org-ref-glossary-enable-multi-file
|
||||
(boundp 'org-ref-glossary-file-cache)
|
||||
(buffer-file-name))
|
||||
(cl-block or-parse-glossary-entry-multi-file
|
||||
(let* ((current-file (buffer-file-name))
|
||||
(included-files (org-ref-get-included-files))
|
||||
(all-files (cons current-file included-files)))
|
||||
|
||||
;; Scan each file (with caching)
|
||||
(dolist (file all-files)
|
||||
;; Scan if file changed OR if not in cache yet
|
||||
(when (or (org-ref-file-changed-p file)
|
||||
(not (gethash file org-ref-glossary-file-cache)))
|
||||
;; File changed or not cached, scan it
|
||||
(let ((file-entries (or-scan-file-for-glossary-table file)))
|
||||
(puthash file file-entries org-ref-glossary-file-cache)
|
||||
(org-ref-mark-file-scanned file)))
|
||||
|
||||
;; Look for label in this file's cache
|
||||
(let ((entries (gethash file org-ref-glossary-file-cache)))
|
||||
(when entries
|
||||
(let ((entry (cl-find label entries
|
||||
:key (lambda (e) (plist-get e :label))
|
||||
:test 'string=)))
|
||||
(when entry
|
||||
(cl-return-from or-parse-glossary-entry-multi-file entry))))))))))
|
||||
|
||||
|
||||
(defun or-parse-acronym-entry-multi-file (label)
|
||||
"Find acronym LABEL in current file or included files.
|
||||
Uses timestamp-based caching to avoid re-scanning unchanged files.
|
||||
Returns plist with :label, :abbrv, :full, :file."
|
||||
(when (and (boundp 'org-ref-glossary-enable-multi-file)
|
||||
org-ref-glossary-enable-multi-file
|
||||
(boundp 'org-ref-acronym-file-cache)
|
||||
(buffer-file-name))
|
||||
(cl-block or-parse-acronym-entry-multi-file
|
||||
(let* ((current-file (buffer-file-name))
|
||||
(included-files (org-ref-get-included-files))
|
||||
(all-files (cons current-file included-files)))
|
||||
|
||||
;; Scan each file (with caching)
|
||||
(dolist (file all-files)
|
||||
;; Scan if file changed OR if not in cache yet
|
||||
(when (or (org-ref-file-changed-p file)
|
||||
(not (gethash file org-ref-acronym-file-cache)))
|
||||
;; File changed or not cached, scan it
|
||||
(let ((file-entries (or-scan-file-for-acronym-table file)))
|
||||
(puthash file file-entries org-ref-acronym-file-cache)
|
||||
(org-ref-mark-file-scanned file)))
|
||||
|
||||
;; Look for label in this file's cache
|
||||
(let ((entries (gethash file org-ref-acronym-file-cache)))
|
||||
(when entries
|
||||
(let ((entry (cl-find label entries
|
||||
:key (lambda (e) (plist-get e :label))
|
||||
:test 'string=)))
|
||||
(when entry
|
||||
(cl-return-from or-parse-acronym-entry-multi-file entry))))))))))
|
||||
|
||||
|
||||
;;** Glossary links
|
||||
|
||||
@@ -276,13 +436,22 @@ changes."
|
||||
set data on text with properties
|
||||
Set face property, and help-echo."
|
||||
(let ((data (or (or-parse-glossary-entry path)
|
||||
(or-parse-acronym-entry path))))
|
||||
(or-parse-acronym-entry path)
|
||||
;; Try multi-file lookup if enabled and not found in current buffer
|
||||
(or-parse-glossary-entry-multi-file path)
|
||||
(or-parse-acronym-entry-multi-file path))))
|
||||
(add-text-properties
|
||||
start end
|
||||
(list 'or-glossary data
|
||||
'face (if data
|
||||
'org-ref-glossary-face
|
||||
'font-lock-warning-face)))))
|
||||
'font-lock-warning-face)
|
||||
;; Suppress spell-checking with nospell property.
|
||||
;; For jinx users: add 'nospell to jinx-exclude-properties:
|
||||
;; (setq jinx-exclude-properties '((org-mode read-only nospell)))
|
||||
;; Or exclude by face using jinx-exclude-faces:
|
||||
;; (add-to-list 'jinx-exclude-faces 'org-ref-glossary-face)
|
||||
'nospell t))))
|
||||
|
||||
(defface org-ref-glossary-face
|
||||
`((t (:inherit org-link :foreground "Mediumpurple3")))
|
||||
@@ -290,28 +459,53 @@ Set face property, and help-echo."
|
||||
|
||||
|
||||
(defun or-follow-glossary (entry)
|
||||
"Goto beginning of the glossary ENTRY."
|
||||
"Goto beginning of the glossary ENTRY.
|
||||
If entry is in an included file, opens that file and navigates to the glossary table."
|
||||
(org-mark-ring-push)
|
||||
(goto-char (plist-get (get-text-property (point) 'or-glossary) :position)))
|
||||
(let* ((data (get-text-property (point) 'or-glossary))
|
||||
(file (plist-get data :file))
|
||||
(label (plist-get data :label))
|
||||
(position (plist-get data :position)))
|
||||
(cond
|
||||
;; Entry in current buffer (has position)
|
||||
(position
|
||||
(goto-char position))
|
||||
;; Entry in external file
|
||||
(file
|
||||
(find-file file)
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^[ \t]*#\\+name:[ \t]+\\(glossary\\|acronyms\\)" nil t)
|
||||
(when (re-search-forward (regexp-quote label) nil t)
|
||||
(goto-char (line-beginning-position)))))
|
||||
;; Fallback: search in current buffer
|
||||
(t
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^[ \t]*#\\+name:[ \t]+\\(glossary\\|acronyms\\)" nil t)
|
||||
(when (re-search-forward (regexp-quote label) nil t)
|
||||
(goto-char (line-beginning-position))))))))
|
||||
|
||||
|
||||
(defun or-glossary-tooltip (_window buffer position)
|
||||
"Return tooltip for the glossary entry.
|
||||
The entry is in WINDOW and OBJECT at POSITION.
|
||||
Used in fontification."
|
||||
(with-current-buffer buffer
|
||||
(let* ((data (get-text-property position 'or-glossary))
|
||||
(name (or (plist-get data :name)
|
||||
(plist-get data :abbrv)))
|
||||
(description (or (plist-get data :description)
|
||||
(plist-get data :full))))
|
||||
(format
|
||||
"%s: %s"
|
||||
name
|
||||
(with-temp-buffer
|
||||
(insert (concat description "."))
|
||||
(fill-paragraph)
|
||||
(buffer-string))))))
|
||||
Used in fontification."
|
||||
(when org-ref-glossary-show-tooltips
|
||||
(with-current-buffer buffer
|
||||
(let ((data (get-text-property position 'or-glossary)))
|
||||
(if data
|
||||
(let ((name (or (plist-get data :name)
|
||||
(plist-get data :abbrv)))
|
||||
(description (or (plist-get data :description)
|
||||
(plist-get data :full))))
|
||||
(when (and name description)
|
||||
(format "%s: %s"
|
||||
name
|
||||
(with-temp-buffer
|
||||
(insert (concat description "."))
|
||||
(fill-paragraph)
|
||||
(buffer-string)))))
|
||||
;; No data found - return helpful message or nil
|
||||
"This is not defined in this file.")))))
|
||||
|
||||
|
||||
(defvar org-ref-glossary-gls-commands
|
||||
@@ -431,13 +625,13 @@ The plist maps to \newacronym{<label>}{<abbrv>}{<full>}"
|
||||
(goto-char (point-min))
|
||||
(let* (abbrv
|
||||
full p1
|
||||
(external (when (re-search-forward "\\loadglsentries\\(\\[.*\\]\\){\\(?1:.*\\)}" nil t)
|
||||
(external (when (re-search-forward "\\\\loadglsentries\\(\\[.*\\]\\)?{\\(?1:.*\\)}" nil t)
|
||||
(match-string 1)))
|
||||
(glsentries (and external
|
||||
(or (cdr (assoc external org-ref-glsentries))
|
||||
(progn
|
||||
(cl-pushnew (cons external
|
||||
(s-trim (shell-command-to-string
|
||||
(string-trim (shell-command-to-string
|
||||
(format "kpsewhich tex %s" external))))
|
||||
org-ref-glsentries)
|
||||
(cdr (assoc external org-ref-glsentries))))))
|
||||
@@ -502,30 +696,50 @@ The plist maps to \newacronym{<label>}{<abbrv>}{<full>}"
|
||||
"Activate function for an acronym link.
|
||||
set data on text with properties
|
||||
Set face property, and help-echo."
|
||||
(let ((data (or-parse-acronym-entry path)))
|
||||
(let ((data (or (or-parse-acronym-entry path)
|
||||
;; Try multi-file lookup if enabled and not found in current buffer
|
||||
(or-parse-acronym-entry-multi-file path))))
|
||||
(add-text-properties
|
||||
start end
|
||||
(list 'or-glossary data
|
||||
'face (if data
|
||||
'org-ref-acronym-face
|
||||
'font-lock-warning-face)))))
|
||||
'font-lock-warning-face)
|
||||
;; Suppress spell-checking with nospell property.
|
||||
;; For jinx users: add 'nospell to jinx-exclude-properties:
|
||||
;; (setq jinx-exclude-properties '((org-mode read-only nospell)))
|
||||
;; Or exclude by face using jinx-exclude-faces:
|
||||
;; (add-to-list 'jinx-exclude-faces 'org-ref-acronym-face)
|
||||
'nospell t))))
|
||||
|
||||
|
||||
(defun or-follow-acronym (label)
|
||||
"Go to the definition of the acronym LABEL."
|
||||
"Go to the definition of the acronym LABEL.
|
||||
If entry is in an included file, opens that file and navigates to the acronym table."
|
||||
(org-mark-ring-push)
|
||||
(cond
|
||||
;; table first
|
||||
((progn (goto-char (point-min))
|
||||
(and (re-search-forward "#\\+name: acronyms" nil t)
|
||||
(re-search-forward label nil t)))
|
||||
nil)
|
||||
|
||||
((progn (goto-char (point-min)) (re-search-forward (format "\\newacronym{%s}" label) nil t))
|
||||
(goto-char (match-beginning 0)))
|
||||
|
||||
(t
|
||||
(message "no entry found for %s" label))))
|
||||
(let* ((data (get-text-property (point) 'or-glossary))
|
||||
(file (plist-get data :file))
|
||||
(entry-label (plist-get data :label)))
|
||||
(cond
|
||||
;; Entry in external file
|
||||
(file
|
||||
(find-file file)
|
||||
(goto-char (point-min))
|
||||
(if (and (re-search-forward "^[ \t]*#\\+name:[ \t]+acronyms" nil t)
|
||||
(re-search-forward (regexp-quote entry-label) nil t))
|
||||
(goto-char (line-beginning-position))
|
||||
(message "Entry %s not found in %s" entry-label file)))
|
||||
;; Entry in current buffer - try table first
|
||||
((progn (goto-char (point-min))
|
||||
(and (re-search-forward "#\\+name: acronyms" nil t)
|
||||
(re-search-forward label nil t)))
|
||||
nil)
|
||||
;; Try LaTeX format
|
||||
((progn (goto-char (point-min))
|
||||
(re-search-forward (format "\\newacronym{%s}" label) nil t))
|
||||
(goto-char (match-beginning 0)))
|
||||
(t
|
||||
(message "no entry found for %s" label)))))
|
||||
|
||||
|
||||
;;** Tooltips on acronyms
|
||||
@@ -539,17 +753,17 @@ Set face property, and help-echo."
|
||||
The entry is in WINDOW and OBJECT at POSITION.
|
||||
Used in fontification.
|
||||
WINDOW and OBJECT are ignored."
|
||||
(with-current-buffer buffer
|
||||
(save-excursion
|
||||
(goto-char position)
|
||||
(let* ((acronym-data (get-text-property position 'or-glossary))
|
||||
(abbrv (plist-get acronym-data :abbrv))
|
||||
(full (plist-get acronym-data :full)))
|
||||
(if acronym-data
|
||||
(format
|
||||
"%s: %s"
|
||||
abbrv full)
|
||||
(format "This is not defined in this file."))))))
|
||||
(when org-ref-glossary-show-tooltips
|
||||
(with-current-buffer buffer
|
||||
(save-excursion
|
||||
(goto-char position)
|
||||
(let ((acronym-data (get-text-property position 'or-glossary)))
|
||||
(if acronym-data
|
||||
(let ((abbrv (plist-get acronym-data :abbrv))
|
||||
(full (plist-get acronym-data :full)))
|
||||
(when (and abbrv full)
|
||||
(format "%s: %s" abbrv full)))
|
||||
"This is not defined in this file."))))))
|
||||
|
||||
|
||||
(defvar org-ref-acronym-types
|
||||
@@ -934,5 +1148,40 @@ Meant for non-LaTeX exports."
|
||||
(make-string (org-element-property :post-blank lnk) ? )))))))
|
||||
|
||||
|
||||
;;** Jinx spell-checker integration
|
||||
|
||||
;; Automatically configure jinx to exclude glossary and acronym links from
|
||||
;; spell-checking if jinx is loaded. This prevents jinx from marking these
|
||||
;; links as typos.
|
||||
;;
|
||||
;; This configuration applies immediately if jinx is already loaded, or will
|
||||
;; take effect when jinx is loaded in the future.
|
||||
(defun org-ref-glossary--configure-jinx ()
|
||||
"Add glossary and acronym faces to jinx-exclude-faces for org-mode."
|
||||
(when (boundp 'jinx-exclude-faces)
|
||||
;; jinx-exclude-faces is an alist: ((mode face1 face2 ...) ...)
|
||||
;; We need to add our faces to the org-mode entry
|
||||
(let ((org-entry (assq 'org-mode jinx-exclude-faces)))
|
||||
(if org-entry
|
||||
;; org-mode entry exists, add our faces to it
|
||||
(progn
|
||||
(unless (memq 'org-ref-glossary-face org-entry)
|
||||
(setcdr org-entry (cons 'org-ref-glossary-face (cdr org-entry))))
|
||||
(unless (memq 'org-ref-acronym-face org-entry)
|
||||
(setcdr org-entry (cons 'org-ref-acronym-face (cdr org-entry)))))
|
||||
;; org-mode entry doesn't exist, create it
|
||||
(push '(org-mode org-ref-glossary-face org-ref-acronym-face)
|
||||
jinx-exclude-faces)))
|
||||
(message "org-ref-glossary: Configured jinx to exclude glossary/acronym faces in org-mode")))
|
||||
|
||||
;; Configure immediately if jinx is already loaded
|
||||
(when (featurep 'jinx)
|
||||
(org-ref-glossary--configure-jinx))
|
||||
|
||||
;; Also configure when jinx loads in the future
|
||||
(with-eval-after-load 'jinx
|
||||
(org-ref-glossary--configure-jinx))
|
||||
|
||||
|
||||
(provide 'org-ref-glossary)
|
||||
;;; org-ref-glossary.el ends here
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
;; Provides functions to download bibtex entries from isbn numbers.
|
||||
|
||||
;;; Code:
|
||||
(require 'f)
|
||||
(require 'org-ref-utils)
|
||||
(require 'org)
|
||||
|
||||
(declare-function org-ref-possible-bibfiles "org-ref-core" ())
|
||||
(declare-function org-ref-clean-bibtex-entry "org-ref-bibtex" ())
|
||||
|
||||
(defvar url-http-end-of-headers)
|
||||
|
||||
@@ -154,12 +156,12 @@ in the file. Data comes from www.ebook.de."
|
||||
(cond
|
||||
;; If region is active and it starts with a number, we use it
|
||||
((and (region-active-p)
|
||||
(s-match "^[0-9]" (buffer-substring (region-beginning) (region-end))))
|
||||
(org-ref--string-match "^[0-9]" (buffer-substring (region-beginning) (region-end))))
|
||||
(buffer-substring (region-beginning) (region-end)))
|
||||
;; if first entry in kill ring starts with a number assume it is an isbn
|
||||
;; and use it as the guess
|
||||
((stringp (car kill-ring))
|
||||
(when (s-match "^[0-9]" (car kill-ring))
|
||||
(when (org-ref--string-match "^[0-9]" (car kill-ring))
|
||||
(car kill-ring)))
|
||||
;; type or paste it in
|
||||
(t
|
||||
@@ -188,7 +190,7 @@ in the file. Data comes from www.ebook.de."
|
||||
(org-ref-isbn-clean-bibtex-entry)
|
||||
(org-ref-clean-bibtex-entry)
|
||||
(bibtex-fill-entry)
|
||||
(s-trim (buffer-string))))
|
||||
(string-trim (buffer-string))))
|
||||
(save-buffer))))
|
||||
|
||||
|
||||
@@ -205,12 +207,12 @@ API: https://openlibrary.org/developers/api
|
||||
(cond
|
||||
;; If region is active and it starts with a number, we use it
|
||||
((and (region-active-p)
|
||||
(s-match "^[0-9]" (buffer-substring (region-beginning) (region-end))))
|
||||
(org-ref--string-match "^[0-9]" (buffer-substring (region-beginning) (region-end))))
|
||||
(buffer-substring (region-beginning) (region-end)))
|
||||
;; if first entry in kill ring starts with a number assume it is an isbn
|
||||
;; and use it as the guess
|
||||
((stringp (car kill-ring))
|
||||
(when (s-match "^[0-9]" (car kill-ring))
|
||||
(when (org-ref--string-match "^[0-9]" (car kill-ring))
|
||||
(car kill-ring)))
|
||||
;; type or paste it in
|
||||
(t
|
||||
@@ -221,19 +223,20 @@ API: https://openlibrary.org/developers/api
|
||||
(json (with-current-buffer (url-retrieve-synchronously url)
|
||||
(json-read-from-string (string-trim (buffer-substring url-http-end-of-headers (point-max))))))
|
||||
(title (cdr (assoc 'title json)))
|
||||
(publisher (s-join ", " (cdr (assoc 'publishers json))))
|
||||
(publisher (string-join (cdr (assoc 'publishers json)) ", "))
|
||||
(year (cdr (assoc 'publish_date json)))
|
||||
;; this is a list of urls
|
||||
(author-urls (cdr (assoc 'authors json)))
|
||||
(authors (s-join " and "
|
||||
(authors (string-join
|
||||
(cl-loop for aurl across author-urls
|
||||
collect
|
||||
(with-current-buffer (url-retrieve-synchronously
|
||||
(format "https://openlibrary.org%s.json"
|
||||
(cdr (assoc 'key aurl))))
|
||||
(cdr (assoc 'key aurl))))
|
||||
(cdr (assoc 'personal_name
|
||||
(json-read-from-string
|
||||
(string-trim (buffer-substring url-http-end-of-headers (point-max))))))))))
|
||||
(string-trim (buffer-substring url-http-end-of-headers (point-max))))))))
|
||||
" and "))
|
||||
(burl (format "https://openlibrary.org%s" (cdr (assoc 'key json))))
|
||||
(bibtex (format "@Book{,
|
||||
author = {%s},
|
||||
|
||||
@@ -28,9 +28,22 @@
|
||||
;; citation selection looks like `ivy-bibtex' but it is a customized ivy
|
||||
;; function with customized actions.
|
||||
|
||||
;; ivy-bibtex is optional, and it causes compiling errors if not installed. I
|
||||
;; think this might fix that.
|
||||
(when (and (bound-and-true-p byte-compile-current-file)
|
||||
(not (locate-library "ivy-bibtex")))
|
||||
(message "org-ref-ivy: skipping byte-compile; ivy-bibtex not installed.")
|
||||
(setq byte-compile-current-file nil)
|
||||
(throw 'byte-compile-top-level nil))
|
||||
|
||||
|
||||
(require 'org-ref-core)
|
||||
(require 'ivy-bibtex)
|
||||
|
||||
;; Declare variable before any potential use (including in macros)
|
||||
;; Initialize with nil to avoid "void variable" error
|
||||
(defvar org-ref-citation-alternate-insert-actions nil
|
||||
"Actions for ivy citation insertion. Set by defcustom below.")
|
||||
|
||||
;; all these functions are defined in ivy-bibtex, but not in a variable. Here
|
||||
;; you can set them as you see fit.
|
||||
@@ -79,7 +92,7 @@
|
||||
(let* ((width (- (frame-width) 2))
|
||||
(idx (get-text-property 1 'idx candidate))
|
||||
(entry (cdr (nth idx (ivy-state-collection ivy-last)))))
|
||||
(s-concat (if (s-starts-with-p ivy-mark-prefix candidate) ivy-mark-prefix "")
|
||||
(concat (if (string-prefix-p ivy-mark-prefix candidate) ivy-mark-prefix "")
|
||||
(bibtex-completion-format-entry entry width))))
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
;;; Code:
|
||||
|
||||
(declare-function org-ref-get-labels "org-ref-ref-links" ())
|
||||
|
||||
|
||||
(defface org-ref-label-face
|
||||
`((t (:inherit org-link :foreground "dark magenta")))
|
||||
|
||||
@@ -68,22 +68,68 @@
|
||||
The clickable part are the keys.")
|
||||
|
||||
|
||||
(defun org-ref-latex-get-bibliography ()
|
||||
"Find bibliographies in the tex file."
|
||||
(defun org-ref-latex--scan-buffer-for-bibliography ()
|
||||
"Scan current buffer for bibliography commands.
|
||||
Returns a list of bibliography files found via \\bibliography{}
|
||||
and \\addbibresource{} commands.
|
||||
|
||||
This is a helper function for `org-ref-latex-get-bibliography'."
|
||||
(save-excursion
|
||||
(let ((bibliography '()))
|
||||
;; Look for \bibliography{file1,file2,...} (BibTeX format)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\\\\bibliography{\\(?1:.*\\)}" nil t)
|
||||
(setq bibliography (append bibliography
|
||||
(mapcar (lambda (f)
|
||||
(concat f ".bib"))
|
||||
(split-string (match-string-no-properties 1) ",")))))
|
||||
(setq bibliography
|
||||
(append bibliography
|
||||
(mapcar (lambda (f)
|
||||
(concat f ".bib"))
|
||||
(split-string (match-string-no-properties 1) ",")))))
|
||||
;; Look for \addbibresource{file.bib} (BibLaTeX format)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\\\\addbibresource{\\(?1:.*\\)}" nil t)
|
||||
(setq bibliography (append bibliography (list (match-string-no-properties 1)))))
|
||||
|
||||
(setq bibliography
|
||||
(append bibliography
|
||||
(list (match-string-no-properties 1)))))
|
||||
bibliography)))
|
||||
|
||||
(defun org-ref-latex-get-bibliography ()
|
||||
"Find bibliographies in the tex file.
|
||||
|
||||
First searches the current buffer for \\bibliography{} or
|
||||
\\addbibresource{} commands.
|
||||
|
||||
If no bibliography is found and the `TeX-master' variable is set
|
||||
to a filename (indicating this is an included file in a multi-file
|
||||
project), searches the master file for bibliography commands.
|
||||
|
||||
This enables bibliography discovery in multi-file LaTeX projects
|
||||
where chapter/section files are included into a main file that
|
||||
contains the bibliography declaration.
|
||||
|
||||
Returns a list of bibliography file paths."
|
||||
(let ((bibliography (org-ref-latex--scan-buffer-for-bibliography)))
|
||||
;; If no bibliography found in current buffer, check TeX-master
|
||||
(when (and (null bibliography)
|
||||
(boundp 'TeX-master)
|
||||
(stringp TeX-master))
|
||||
;; TeX-master is a filename (relative or absolute)
|
||||
;; Build the full path to the master file
|
||||
(let* ((master-file
|
||||
(expand-file-name
|
||||
;; Add .tex extension if not present
|
||||
(if (string-match-p "\\.tex\\'" TeX-master)
|
||||
TeX-master
|
||||
(concat TeX-master ".tex"))
|
||||
;; Resolve relative to current buffer's directory
|
||||
(file-name-directory (buffer-file-name))))
|
||||
(master-buffer
|
||||
(when (file-exists-p master-file)
|
||||
(find-file-noselect master-file))))
|
||||
(when master-buffer
|
||||
(with-current-buffer master-buffer
|
||||
(setq bibliography (org-ref-latex--scan-buffer-for-bibliography))))))
|
||||
bibliography))
|
||||
|
||||
|
||||
(defun org-ref-next-latex-cite (&optional limit)
|
||||
"Font-lock function to make cites in LaTeX documents clickable."
|
||||
@@ -101,14 +147,14 @@ The clickable part are the keys.")
|
||||
:optional-1 ,(match-string-no-properties 2)
|
||||
:optional-2 ,(match-string-no-properties 3)
|
||||
:keys ,(mapcar
|
||||
#'s-trim
|
||||
(s-split
|
||||
","
|
||||
(match-string-no-properties 4))))))
|
||||
|
||||
#'string-trim
|
||||
(split-string
|
||||
(match-string-no-properties 4)
|
||||
",")))))
|
||||
|
||||
(goto-char (match-beginning 0))
|
||||
(let ((end (match-end 0)))
|
||||
(cl-loop for key in (mapcar #'s-trim (split-string (match-string-no-properties 4) ","))
|
||||
(cl-loop for key in (mapcar #'string-trim (split-string (match-string-no-properties 4) ","))
|
||||
unless (string-empty-p key)
|
||||
do
|
||||
(save-match-data
|
||||
@@ -212,7 +258,7 @@ Should DWIM inserting into existing or new citations."
|
||||
(setq cite-string (concat (format "\\%s" (plist-get data :cite-type))
|
||||
(or (plist-get data :optional-1) "")
|
||||
(or (plist-get data :optional-2) "")
|
||||
(format "{%s}" (s-join "," (plist-get data :keys)))))
|
||||
(format "{%s}" (string-join (plist-get data :keys) ","))))
|
||||
(cl--set-buffer-substring (plist-get data :cite-begin)
|
||||
(plist-get data :cite-end)
|
||||
cite-string)
|
||||
@@ -263,7 +309,7 @@ With prefix arg DESCENDING, sort in descending order."
|
||||
(setq cite-string (concat (format "\\%s" (plist-get data :cite-type))
|
||||
(or (plist-get data :optional-1) "")
|
||||
(or (plist-get data :optional-2) "")
|
||||
(format "{%s}" (s-join "," (plist-get data :keys)))))
|
||||
(format "{%s}" (string-join (plist-get data :keys) ","))))
|
||||
(cl--set-buffer-substring (plist-get data :cite-begin)
|
||||
(plist-get data :cite-end)
|
||||
cite-string)
|
||||
|
||||
@@ -52,7 +52,7 @@ Ignore figures in COMMENTED sections."
|
||||
(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)))))
|
||||
(seq-intersection (org-get-tags) org-export-exclude-tags)))))
|
||||
(cl-incf counter)
|
||||
|
||||
(let* ((start (org-element-property :begin link))
|
||||
@@ -112,7 +112,7 @@ ARG does nothing."
|
||||
(when
|
||||
;; ignore commented sections
|
||||
(not (or (org-in-commented-heading-p)
|
||||
(-intersection (org-get-tags) org-export-exclude-tags)
|
||||
(seq-intersection (org-get-tags) org-export-exclude-tags)
|
||||
(looking-at "#\\+RESULTS:") ))
|
||||
(cl-incf counter)
|
||||
(let* ((start (org-element-property :begin table))
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
(declare-function org-ref-get-cite-links "org-ref-core" ())
|
||||
(declare-function org-ref-parse-cite-path "org-ref-core" (path))
|
||||
|
||||
(defvar org-ref-natmove) ;silence compiler
|
||||
|
||||
(defun org-bbl-clean-string (s)
|
||||
@@ -141,7 +144,7 @@ cover any math (yet)."
|
||||
"Return natbib-options (including the brackets).
|
||||
Defaults to [numbers,super,sort]"
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward "\\usepackage\\(?1:\\[.*\\]\\)?{natbib}")
|
||||
(if (re-search-forward "\\\\usepackage\\(?1:\\[.*\\]\\)?{natbib}")
|
||||
(match-string 1)
|
||||
"[numbers,super,sort]"))
|
||||
|
||||
@@ -475,10 +478,10 @@ bibliography.
|
||||
natbib-options bibdata org-ref-natmove
|
||||
buf)
|
||||
|
||||
(when-let (buf (find-buffer-visiting tex-file))
|
||||
(when-let* (buf (find-buffer-visiting tex-file))
|
||||
(kill-buffer buf))
|
||||
|
||||
(when-let (buf (find-buffer-visiting bbl-file))
|
||||
(when-let* (buf (find-buffer-visiting bbl-file))
|
||||
(kill-buffer buf))
|
||||
|
||||
;; refresh these
|
||||
|
||||
@@ -32,14 +32,26 @@
|
||||
;; get a doi. This needs a reliable title/citation.
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; pdf-tools is optional, and I think this will skip compiling this file and
|
||||
;; outputting an error.
|
||||
(when (and (bound-and-true-p byte-compile-current-file)
|
||||
(not (locate-library "pdf-tools")))
|
||||
(message "org-ref-pdf: skipping byte-compile; pdf-tools not installed.")
|
||||
(setq byte-compile-current-file nil)
|
||||
(throw 'byte-compile-top-level nil))
|
||||
|
||||
(require 'pdf-tools)
|
||||
|
||||
(require 'f)
|
||||
(require 'org-ref-utils)
|
||||
|
||||
|
||||
(eval-when-compile
|
||||
(require 'cl-lib))
|
||||
|
||||
(declare-function org-ref-normalize-bibtex-completion-bibliography "org-ref-utils")
|
||||
|
||||
(defvar bibtex-completion-bibliography)
|
||||
|
||||
(defgroup org-ref-pdf nil
|
||||
"Customization group for org-ref-pdf"
|
||||
@@ -88,7 +100,7 @@ strings, or nil."
|
||||
;; I don't know how to avoid a trailing . on some dois with the
|
||||
;; expression above, so if it is there, I chomp it off here.
|
||||
(let ((doi (match-string 0)))
|
||||
(when (or (s-ends-with? "." doi) (s-ends-with? ";" doi))
|
||||
(when (or (string-suffix-p "." doi) (string-suffix-p ";" doi))
|
||||
(setq doi (substring doi 0 (- (length doi) 1))))
|
||||
(cl-pushnew doi matches :test #'equal)))
|
||||
matches)))
|
||||
@@ -110,9 +122,7 @@ Used when multiple dois are found in a pdf file."
|
||||
"Return a bibtex entry's key from a DOI.
|
||||
BIB is an optional filename to get the entry from."
|
||||
(catch 'key
|
||||
(cl-loop for bibfile in (if (stringp bibtex-completion-bibliography)
|
||||
(list bibtex-completion-bibliography)
|
||||
bibtex-completion-bibliography)
|
||||
(cl-loop for bibfile in (org-ref-normalize-bibtex-completion-bibliography)
|
||||
do
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (expand-file-name bibfile))
|
||||
@@ -126,7 +136,7 @@ BIB is an optional filename to get the entry from."
|
||||
"Add pdf of current buffer to bib file and save pdf. The pdf
|
||||
should be open in Emacs using the `pdf-tools' package."
|
||||
(interactive)
|
||||
(when (not (f-ext? (downcase (buffer-file-name)) "pdf"))
|
||||
(when (not (org-ref--file-ext-p (downcase (buffer-file-name)) "pdf"))
|
||||
(error "Buffer is not a pdf file"))
|
||||
;; Get doi from pdf of current buffer
|
||||
(let* ((dois (org-ref-extract-doi-from-pdf (buffer-file-name)))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'((dash "0")
|
||||
(s "0")
|
||||
(f "0")
|
||||
(hydra "0")
|
||||
(transient "0")
|
||||
(htmlize "0")
|
||||
(parsebib "0")
|
||||
(bibtex-completion "0")
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
;; -*- no-byte-compile: t; lexical-binding: nil -*-
|
||||
(define-package "org-ref" "20250301.1918"
|
||||
(define-package "org-ref" "20251123.1710"
|
||||
"Citations, cross-references and bibliographies in org-mode."
|
||||
'((org "9.4")
|
||||
(dash "0")
|
||||
(s "0")
|
||||
(f "0")
|
||||
(htmlize "0")
|
||||
(hydra "0")
|
||||
(transient "0")
|
||||
(avy "0")
|
||||
(parsebib "0")
|
||||
(bibtex-completion "0")
|
||||
@@ -14,8 +11,8 @@
|
||||
(ox-pandoc "0")
|
||||
(request "0"))
|
||||
:url "https://github.com/jkitchin/org-ref"
|
||||
:commit "edbb80863ef63ef52ef04fce3239e063843f8d30"
|
||||
:revdesc "edbb80863ef6"
|
||||
:commit "5275e58bca7e36c588e519c1b3ebe1e6b28a0654"
|
||||
:revdesc "5275e58bca7e"
|
||||
:keywords '("org-mode" "cite" "ref" "label")
|
||||
:authors '(("John Kitchin" . "jkitchin@andrew.cmu.edu"))
|
||||
:maintainers '(("John Kitchin" . "jkitchin@andrew.cmu.edu")))
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
;;; Commentary:
|
||||
;; First draft proposed by madhu 230826 (Madhu <enometh@meer.net>)
|
||||
;; This code provides publishing capability that leverages
|
||||
;; This code provides publishing capability that leverages
|
||||
|
||||
;;; Code:
|
||||
|
||||
(declare-function org-ref-process-buffer "org-ref-core" (backend &optional subtreep))
|
||||
|
||||
;; modified from org-publish-org-to to call org-ref-process-buffer before
|
||||
;; calling org-export-to-file
|
||||
(defun org-ref-publish-org-to (backend filename extension plist &optional pub-dir)
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
;; is a unique reference number for PubMed citations. The PMID is a distinctly
|
||||
;; different number from the PMCID and is used only for PubMed records.
|
||||
|
||||
(require 'dash)
|
||||
(require 'org)
|
||||
(require 'org-ref-utils)
|
||||
|
||||
@@ -115,7 +114,7 @@
|
||||
(let* ((data (pubmed-parse-medline pmid))
|
||||
(type (downcase (cdr (assoc "PT" data))))
|
||||
(title (cdr (assoc "TI" data)))
|
||||
(authors (mapconcat 'cdr
|
||||
(authors (mapconcat 'cdr
|
||||
(seq-filter (lambda (x)
|
||||
(string= (car x) "FAU"))
|
||||
data)
|
||||
|
||||
@@ -24,7 +24,23 @@
|
||||
;;; Code:
|
||||
(eval-and-compile (require 'org-macs))
|
||||
(eval-and-compile (require 'ol))
|
||||
(require 'hydra)
|
||||
|
||||
;; Declare functions from org-element
|
||||
(declare-function org-element-context "org-element" (&optional element))
|
||||
(declare-function org-element-property "org-element" (property element))
|
||||
(declare-function org-element-type "org-element" (element))
|
||||
(declare-function org-element-parse-buffer "org-element" (&optional granularity visible-only))
|
||||
(declare-function org-element-map "org-element" (data types fun &optional info first-match no-recursion with-affiliated))
|
||||
(declare-function org-element-interpret-data "org-element" (data))
|
||||
|
||||
;; Declare functions from org
|
||||
(declare-function org-mark-ring-push "org" (&optional pos buffer))
|
||||
(declare-function org-file-image-p "org" (file &optional extensions))
|
||||
(declare-function org-at-table-p "org" (&optional table-type))
|
||||
(declare-function org-table-begin "org" (&optional table-type))
|
||||
|
||||
;; Declare functions from ox (org-export)
|
||||
(declare-function org-export-get-caption "ox" (element &optional short))
|
||||
|
||||
(defcustom org-ref-default-ref-type "ref"
|
||||
"Default ref link type to use when inserting ref links."
|
||||
@@ -40,6 +56,36 @@ links. Set this to nil to turn off activation."
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defcustom org-ref-show-equation-images-in-tooltips nil
|
||||
"If non-nil, show rendered equation images in tooltips for equation references.
|
||||
Requires that equations have been previewed with `org-latex-preview' or
|
||||
a compatible preview package (like math-preview).
|
||||
|
||||
When enabled, hovering over an equation reference (eqref, ref, etc.) will
|
||||
display the rendered equation image in a tooltip instead of raw LaTeX code.
|
||||
|
||||
This feature works best in GUI Emacs. In terminal Emacs, falls back to
|
||||
text display."
|
||||
:type 'boolean
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defcustom org-ref-enable-multi-file-references t
|
||||
"If non-nil, collect labels from files included via #+INCLUDE directives.
|
||||
|
||||
When enabled, org-ref will search for labels not only in the current buffer,
|
||||
but also in all files referenced via #+INCLUDE directives. This allows
|
||||
cross-references to work across multiple files in a project.
|
||||
|
||||
Labels are cached per-file and only re-scanned when files change, using
|
||||
timestamp-based change detection for performance.
|
||||
|
||||
For single-file documents, this feature has minimal overhead since no
|
||||
#+INCLUDE directives are present. Set to nil to disable if needed."
|
||||
:type 'boolean
|
||||
:group 'org-ref)
|
||||
|
||||
|
||||
(defface org-ref-ref-face
|
||||
`((t (:inherit org-link :foreground "dark red")))
|
||||
"Face for ref links in org-ref."
|
||||
@@ -132,6 +178,162 @@ The label should always be in group 1.")
|
||||
"Buffer-local variable to hold `buffer-chars-modified-tick'.")
|
||||
|
||||
|
||||
(defvar-local org-ref-preview-image-cache nil
|
||||
"Buffer-local cache of (label . image-spec) for preview images.")
|
||||
|
||||
|
||||
(defvar-local org-ref-preview-cache-tick nil
|
||||
"Buffer modification tick when preview image cache was last updated.")
|
||||
|
||||
|
||||
;; Multi-file reference support - global caches
|
||||
(defvar org-ref-project-label-cache (make-hash-table :test 'equal)
|
||||
"Hash table mapping project-root -> ((file . labels-alist) ...).
|
||||
Used when `org-ref-enable-multi-file-references' is non-nil to cache
|
||||
labels from multiple files in a project.")
|
||||
|
||||
|
||||
(defvar org-ref-file-timestamps (make-hash-table :test 'equal)
|
||||
"Hash table mapping file-path -> (mtime . size) for change detection.
|
||||
Used to determine if a file needs to be re-scanned for labels without
|
||||
actually opening and parsing the file.")
|
||||
|
||||
|
||||
(defun org-ref-get-included-files ()
|
||||
"Return list of absolute paths to files included in current buffer.
|
||||
Parses #+INCLUDE directives and returns a list of existing files.
|
||||
Only returns files that actually exist on disk."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let ((files '()))
|
||||
(while (re-search-forward "^[ \t]*#\\+INCLUDE:[ \t]+\"\\([^\"]+\\)\"" nil t)
|
||||
(let* ((file (match-string-no-properties 1))
|
||||
(expanded-file (expand-file-name file)))
|
||||
(when (file-exists-p expanded-file)
|
||||
(push expanded-file files))))
|
||||
(nreverse files))))
|
||||
|
||||
|
||||
(defun org-ref-file-changed-p (file)
|
||||
"Check if FILE has changed since last scan using timestamp and size.
|
||||
This is an O(1) operation that doesn't require parsing the file.
|
||||
Returns t if the file has changed or hasn't been scanned yet."
|
||||
(let* ((attrs (file-attributes file))
|
||||
(mtime (nth 5 attrs))
|
||||
(size (nth 7 attrs))
|
||||
(cached (gethash file org-ref-file-timestamps)))
|
||||
(or (not cached)
|
||||
(not (equal mtime (car cached)))
|
||||
(not (equal size (cdr cached))))))
|
||||
|
||||
|
||||
(defun org-ref-mark-file-scanned (file)
|
||||
"Record timestamp and size of FILE to detect future changes."
|
||||
(let* ((attrs (file-attributes file))
|
||||
(mtime (nth 5 attrs))
|
||||
(size (nth 7 attrs)))
|
||||
(puthash file (cons mtime size) org-ref-file-timestamps)))
|
||||
|
||||
|
||||
(defun org-ref-scan-buffer-for-labels ()
|
||||
"Scan current buffer for labels and return list of (label . context) cons cells.
|
||||
This is the core scanning logic used by both single-file and multi-file modes."
|
||||
(let ((case-fold-search t)
|
||||
(rx (string-join org-ref-ref-label-regexps "\\|"))
|
||||
(labels '())
|
||||
oe ;; org-element
|
||||
context)
|
||||
(save-excursion
|
||||
(org-with-wide-buffer
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward rx nil t)
|
||||
(save-match-data
|
||||
;; Here we try to get some relevant context for different things you
|
||||
;; might reference.
|
||||
(setq oe (org-element-context)
|
||||
context (string-trim
|
||||
(pcase (car oe)
|
||||
('latex-environment (buffer-substring
|
||||
(org-element-property :begin oe)
|
||||
(org-element-property :end oe)))
|
||||
;; figure
|
||||
('paragraph (buffer-substring
|
||||
(org-element-property :begin oe)
|
||||
(org-element-property :end oe)))
|
||||
('table (buffer-substring
|
||||
(org-element-property :begin oe)
|
||||
(org-element-property :end oe)))
|
||||
;; Headings fall here.
|
||||
(_ (buffer-substring (line-beginning-position)
|
||||
(line-end-position)))))))
|
||||
(cl-pushnew (cons (match-string-no-properties 1) context)
|
||||
labels))))
|
||||
;; reverse so they are in the order we find them.
|
||||
(delete-dups (reverse labels))))
|
||||
|
||||
|
||||
(defun org-ref-get-labels-single-file ()
|
||||
"Get labels from current buffer only (original single-file behavior).
|
||||
Uses buffer-local cache and buffer-chars-modified-tick for invalidation."
|
||||
(if (or
|
||||
;; if we have not checked we have to check
|
||||
(null org-ref-buffer-chars-modified-tick)
|
||||
;; Now check if buffer has changed since last time we looked. We check
|
||||
;; this with the buffer-chars-modified-tick which keeps track of changes.
|
||||
;; If this hasn't changed, no chars have been modified.
|
||||
(not (= (buffer-chars-modified-tick)
|
||||
org-ref-buffer-chars-modified-tick)))
|
||||
;; We need to search for all the labels either because we don't have them,
|
||||
;; or the buffer has changed since we looked last time.
|
||||
(setq
|
||||
org-ref-buffer-chars-modified-tick (buffer-chars-modified-tick)
|
||||
org-ref-label-cache (org-ref-scan-buffer-for-labels))
|
||||
;; retrieve the cached data
|
||||
org-ref-label-cache))
|
||||
|
||||
|
||||
(defun org-ref-scan-file-for-labels (file)
|
||||
"Scan FILE for labels and return list of (label . context) cons cells.
|
||||
Opens the file in a temporary buffer and scans it."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-mode)
|
||||
(org-ref-scan-buffer-for-labels)))
|
||||
|
||||
|
||||
(defun org-ref-get-labels-multi-file ()
|
||||
"Get labels from current file and all included files.
|
||||
Only re-scans files that have actually changed (timestamp-based detection).
|
||||
Uses global project cache for efficiency."
|
||||
(when (buffer-file-name)
|
||||
(let* ((current-file (buffer-file-name))
|
||||
(included-files (org-ref-get-included-files))
|
||||
(all-files (cons current-file included-files))
|
||||
(all-labels '()))
|
||||
|
||||
;; For each file, check if it changed and re-scan only if needed
|
||||
(dolist (file all-files)
|
||||
(when (org-ref-file-changed-p file)
|
||||
;; File changed, re-scan it
|
||||
(let ((file-labels (if (string= file current-file)
|
||||
;; For current file, use the regular scan
|
||||
(org-ref-scan-buffer-for-labels)
|
||||
;; For included files, scan from disk
|
||||
(org-ref-scan-file-for-labels file))))
|
||||
;; Cache the labels for this file
|
||||
(puthash file file-labels org-ref-project-label-cache)
|
||||
;; Mark file as scanned
|
||||
(org-ref-mark-file-scanned file)))
|
||||
|
||||
;; Retrieve cached labels for this file
|
||||
(let ((file-labels (gethash file org-ref-project-label-cache)))
|
||||
(when file-labels
|
||||
(setq all-labels (append all-labels file-labels)))))
|
||||
|
||||
;; Remove duplicates (in case same label appears in multiple files)
|
||||
(delete-dups all-labels))))
|
||||
|
||||
|
||||
(defun org-ref-get-labels ()
|
||||
"Return a list of referenceable labels in the document.
|
||||
You can reference:
|
||||
@@ -146,60 +348,48 @@ See `org-ref-ref-label-regexps' for the patterns that find these.
|
||||
|
||||
Returns a list of cons cells (label . context).
|
||||
|
||||
If `org-ref-enable-multi-file-references' is non-nil, also includes
|
||||
labels from files referenced via #+INCLUDE directives.
|
||||
|
||||
It is important for this function to be fast, since we use it in
|
||||
font-lock."
|
||||
(if (or
|
||||
;; if we have not checked we have to check
|
||||
(null org-ref-buffer-chars-modified-tick)
|
||||
;; Now check if buffer has changed since last time we looked. We check
|
||||
;; this with the buffer-chars-modified-tick which keeps track of changes.
|
||||
;; If this hasn't changed, no chars have been modified.
|
||||
(not (= (buffer-chars-modified-tick)
|
||||
org-ref-buffer-chars-modified-tick)))
|
||||
;; We need to search for all the labels either because we don't have them,
|
||||
;; or the buffer has changed since we looked last time.
|
||||
(let ((case-fold-search t)
|
||||
(rx (string-join org-ref-ref-label-regexps "\\|"))
|
||||
(labels '())
|
||||
oe ;; org-element
|
||||
context)
|
||||
(save-excursion
|
||||
(org-with-wide-buffer
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward rx nil t)
|
||||
(save-match-data
|
||||
;; Here we try to get some relevant context for different things you
|
||||
;; might reference.
|
||||
(setq oe (org-element-context)
|
||||
context (string-trim
|
||||
(pcase (car oe)
|
||||
('latex-environment (buffer-substring
|
||||
(org-element-property :begin oe)
|
||||
(org-element-property :end oe)))
|
||||
;; figure
|
||||
('paragraph (buffer-substring
|
||||
(org-element-property :begin oe)
|
||||
(org-element-property :end oe)))
|
||||
('table (buffer-substring
|
||||
(org-element-property :begin oe)
|
||||
(org-element-property :end oe)))
|
||||
;; Headings fall here.
|
||||
(_ (buffer-substring (line-beginning-position)
|
||||
(line-end-position)))))))
|
||||
(cl-pushnew (cons (match-string-no-properties 1) context)
|
||||
labels))))
|
||||
|
||||
;; reverse so they are in the order we find them.
|
||||
(setq
|
||||
org-ref-buffer-chars-modified-tick (buffer-chars-modified-tick)
|
||||
org-ref-label-cache (delete-dups (reverse labels))))
|
||||
(if org-ref-enable-multi-file-references
|
||||
(org-ref-get-labels-multi-file)
|
||||
(org-ref-get-labels-single-file)))
|
||||
|
||||
;; retrieve the cached data
|
||||
org-ref-label-cache))
|
||||
|
||||
(defun org-ref-find-label-in-buffer (label)
|
||||
"Search for LABEL in current buffer.
|
||||
Returns t if found and moves point to the label, nil otherwise."
|
||||
(let ((case-fold-search t)
|
||||
(rx (string-join org-ref-ref-label-regexps "\\|")))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(catch 'found
|
||||
(while (re-search-forward rx nil t)
|
||||
(when (string= label (match-string-no-properties 1))
|
||||
(goto-char (match-beginning 1))
|
||||
(throw 'found t)))))))
|
||||
|
||||
|
||||
(defun org-ref-find-label-in-file (label file)
|
||||
"Search for LABEL in FILE.
|
||||
Returns the position if found, nil otherwise."
|
||||
(with-current-buffer (find-file-noselect file)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let ((case-fold-search t)
|
||||
(rx (string-join org-ref-ref-label-regexps "\\|")))
|
||||
(catch 'found
|
||||
(while (re-search-forward rx nil t)
|
||||
(when (string= label (match-string-no-properties 1))
|
||||
(throw 'found (match-beginning 1)))))))))
|
||||
|
||||
|
||||
(defun org-ref-ref-jump-to (&optional path)
|
||||
"Jump to the target for the ref link at point."
|
||||
"Jump to the target for the ref link at point.
|
||||
If `org-ref-enable-multi-file-references' is non-nil and the label
|
||||
is not found in the current buffer, searches in included files."
|
||||
(interactive)
|
||||
(let ((case-fold-search t)
|
||||
(label (get-text-property (point) 'org-ref-ref-label))
|
||||
@@ -213,23 +403,163 @@ font-lock."
|
||||
(setq label (completing-read "Label: " labels)))))
|
||||
(when label
|
||||
(org-mark-ring-push)
|
||||
;; First, try to find in current buffer
|
||||
(widen)
|
||||
(goto-char (point-min))
|
||||
(catch 'found
|
||||
(while (re-search-forward rx)
|
||||
(when (string= label (match-string-no-properties 1))
|
||||
(save-match-data (org-mark-ring-push))
|
||||
(goto-char (match-beginning 1))
|
||||
(org-fold-show-entry)
|
||||
(substitute-command-keys
|
||||
"Go back with (org-mark-ring-goto) \`\\[org-mark-ring-goto]'.")
|
||||
(throw 'found t)))))))
|
||||
(let ((found nil))
|
||||
(catch 'found
|
||||
(while (re-search-forward rx nil t)
|
||||
(when (string= label (match-string-no-properties 1))
|
||||
(save-match-data (org-mark-ring-push))
|
||||
(goto-char (match-beginning 1))
|
||||
(org-fold-show-entry)
|
||||
(message
|
||||
(substitute-command-keys
|
||||
"Go back with (org-mark-ring-goto) \\[org-mark-ring-goto]."))
|
||||
(setq found t)
|
||||
(throw 'found t))))
|
||||
|
||||
;; If not found in current buffer and multi-file mode is enabled,
|
||||
;; search in included files
|
||||
(when (and (not found)
|
||||
org-ref-enable-multi-file-references
|
||||
(buffer-file-name))
|
||||
(let ((included-files (org-ref-get-included-files)))
|
||||
(catch 'found-in-file
|
||||
(dolist (file included-files)
|
||||
(let ((pos (org-ref-find-label-in-file label file)))
|
||||
(when pos
|
||||
;; Found in an included file - open it and jump to position
|
||||
(find-file file)
|
||||
(goto-char pos)
|
||||
(org-fold-show-entry)
|
||||
(message
|
||||
(substitute-command-keys
|
||||
"Go back with (org-mark-ring-goto) \\[org-mark-ring-goto]."))
|
||||
(throw 'found-in-file t)))))
|
||||
|
||||
;; If we get here, label wasn't found anywhere
|
||||
(unless found
|
||||
(message "Label '%s' not found in current file or included files" label))))))))
|
||||
|
||||
|
||||
|
||||
(defun org-ref-find-overlay-with-image (begin end)
|
||||
"Find an overlay with an image display property between BEGIN and END.
|
||||
Returns the image display spec or nil if none found."
|
||||
(catch 'found
|
||||
(let ((pos begin))
|
||||
(while (< pos end)
|
||||
(dolist (ov (overlays-at pos))
|
||||
;; Check for org-mode preview overlay or math-preview overlay
|
||||
(when (or (eq (overlay-get ov 'org-overlay-type) 'org-latex-overlay)
|
||||
(overlay-get ov 'preview-image))
|
||||
(let ((display (overlay-get ov 'display)))
|
||||
(when (and display (listp display) (eq (car display) 'image))
|
||||
(throw 'found display)))))
|
||||
(setq pos (next-overlay-change pos))))
|
||||
nil))
|
||||
|
||||
|
||||
(defun org-ref-get-preview-image-at-label (label)
|
||||
"Return the preview image display spec for LABEL if it exists.
|
||||
Searches for LABEL in the buffer (both \\label{LABEL} and #+name: LABEL forms)
|
||||
and checks if there is an org-latex preview overlay at that location.
|
||||
Returns the image display spec that can be used in a propertized string,
|
||||
or nil if no preview exists.
|
||||
|
||||
Uses caching for performance - cache is invalidated when buffer is modified."
|
||||
;; Check cache first
|
||||
(let ((cached (and org-ref-preview-image-cache
|
||||
(equal org-ref-preview-cache-tick (buffer-chars-modified-tick))
|
||||
(assoc label org-ref-preview-image-cache))))
|
||||
(if cached
|
||||
(cdr cached)
|
||||
;; Not in cache, search for it
|
||||
(let ((image-spec
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(widen)
|
||||
(goto-char (point-min))
|
||||
(or
|
||||
;; Try 1: Search for \label{LABEL} in LaTeX environments
|
||||
(when (re-search-forward (format "\\\\label{%s}" (regexp-quote label)) nil t)
|
||||
(let* ((elem (org-element-context))
|
||||
(begin (org-element-property :begin elem))
|
||||
(end (org-element-property :end elem)))
|
||||
(when (and begin end (eq (org-element-type elem) 'latex-environment))
|
||||
(org-ref-find-overlay-with-image begin end))))
|
||||
|
||||
;; Try 2: Search for #+name: LABEL before latex-environment
|
||||
(progn
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward (format "^[ \t]*#\\+name:[ \t]+%s[ \t]*$"
|
||||
(regexp-quote label)) nil t)
|
||||
(forward-line 1)
|
||||
(let* ((elem (org-element-context))
|
||||
(begin (org-element-property :begin elem))
|
||||
(end (org-element-property :end elem)))
|
||||
(when (and begin end (eq (org-element-type elem) 'latex-environment))
|
||||
(org-ref-find-overlay-with-image begin end))))))))))
|
||||
|
||||
;; Update cache
|
||||
(when (or (null org-ref-preview-cache-tick)
|
||||
(not (equal org-ref-preview-cache-tick (buffer-chars-modified-tick))))
|
||||
(setq org-ref-preview-image-cache nil
|
||||
org-ref-preview-cache-tick (buffer-chars-modified-tick)))
|
||||
|
||||
;; Add to cache (even if nil, to avoid repeated searches)
|
||||
(push (cons label image-spec) org-ref-preview-image-cache)
|
||||
|
||||
image-spec))))
|
||||
|
||||
|
||||
(defun org-ref-ref-help-echo (_win _obj position)
|
||||
"Tooltip for context on a ref label.
|
||||
POSITION is the point under the mouse I think."
|
||||
(cdr (assoc (get-text-property position 'org-ref-ref-label) (org-ref-get-labels))))
|
||||
"Tooltip for context on a ref label with optional image support.
|
||||
POSITION is the point under the mouse.
|
||||
|
||||
Strategy: If previews exist on equations, show them in the minibuffer.
|
||||
Otherwise show text.
|
||||
|
||||
When `org-ref-show-equation-images-in-tooltips' is non-nil and running in
|
||||
GUI Emacs, checks if a preview image exists for the referenced equation.
|
||||
If found, displays the image in the minibuffer using message. Otherwise,
|
||||
displays the LaTeX text context as usual.
|
||||
|
||||
Always returns a string (empty string if no context is found)."
|
||||
(let* ((label (get-text-property position 'org-ref-ref-label))
|
||||
(context (cdr (assoc label (org-ref-get-labels))))
|
||||
(fallback (if label
|
||||
(format "Reference to %s" label)
|
||||
"")))
|
||||
;; Try to show image if conditions are met
|
||||
(or (if (and org-ref-show-equation-images-in-tooltips
|
||||
label
|
||||
(display-graphic-p)) ; Images only work in GUI
|
||||
;; Try to find and display preview image
|
||||
(let ((image-spec (org-ref-get-preview-image-at-label label)))
|
||||
(if image-spec
|
||||
;; Extract file from the overlay's image spec
|
||||
(let ((file (plist-get (cdr image-spec) :file)))
|
||||
;; Only show image if file exists
|
||||
(if (and file (file-exists-p file))
|
||||
(condition-case nil
|
||||
;; Show image in minibuffer using message
|
||||
;; Use the same image-spec from the overlay to preserve size
|
||||
(progn
|
||||
(message "%s" (propertize " " 'display image-spec))
|
||||
;; Still return context for any fallback tooltip
|
||||
context)
|
||||
;; If image display fails, show text
|
||||
(error context))
|
||||
;; File doesn't exist, show text
|
||||
context))
|
||||
;; No image spec found, show text
|
||||
context))
|
||||
;; Feature disabled or not in GUI, show text
|
||||
context)
|
||||
;; Fallback when context is nil
|
||||
fallback)))
|
||||
|
||||
|
||||
(defun org-ref-ref-activate (start _end path _bracketp)
|
||||
@@ -305,10 +635,10 @@ This is meant to be used with `apply-partially' in the link definitions."
|
||||
;; caption paragraph may have a name which we use if it is there
|
||||
(org-element-property :name parent)
|
||||
;; else search caption
|
||||
(let ((caption (s-join
|
||||
""
|
||||
(let ((caption (string-join
|
||||
(mapcar 'org-no-properties
|
||||
(org-export-get-caption parent)))))
|
||||
(org-export-get-caption parent))
|
||||
"")))
|
||||
(when (string-match org-ref-label-re caption)
|
||||
(match-string 1 caption))))))))
|
||||
|
||||
@@ -319,8 +649,8 @@ This is meant to be used with `apply-partially' in the link definitions."
|
||||
(org-element-property :name object)
|
||||
|
||||
;; See if it is in the caption name
|
||||
(let ((caption (s-join "" (mapcar 'org-no-properties
|
||||
(org-export-get-caption object)))))
|
||||
(let ((caption (string-join (mapcar 'org-no-properties
|
||||
(org-export-get-caption object)) "")))
|
||||
(when (string-match org-ref-label-re caption)
|
||||
(match-string 1 caption)))))
|
||||
|
||||
@@ -332,7 +662,7 @@ This is meant to be used with `apply-partially' in the link definitions."
|
||||
(goto-char (org-table-begin))
|
||||
(let* ((table (org-element-context))
|
||||
(label (org-element-property :name table))
|
||||
(caption (s-join "" (mapcar 'org-no-properties
|
||||
(caption (string-join "" (mapcar 'org-no-properties
|
||||
(org-export-get-caption table)))))
|
||||
(when (null label)
|
||||
;; maybe there is a label in the caption?
|
||||
|
||||
@@ -162,7 +162,7 @@ environments)."
|
||||
(org-element-property :name e)
|
||||
(string= "lemma" (org-element-property :type e)))
|
||||
(org-element-property :name e))
|
||||
|
||||
|
||||
((and (eq type 'corollary)
|
||||
(org-element-property :name e)
|
||||
(string= "corollary" (org-element-property :type e)))
|
||||
@@ -176,11 +176,11 @@ environments)."
|
||||
|
||||
;; Listings of code blocks
|
||||
('src-block
|
||||
(when-let (name (org-element-property :name e))
|
||||
(when-let* ((name (org-element-property :name e)))
|
||||
name)))))))))
|
||||
;; the align equation environment needs to be flattened
|
||||
(cl-loop for type in referencables
|
||||
collect (cons (car type) (-flatten (cdr type))))))
|
||||
collect (cons (car type) (org-ref--flatten-list (cdr type))))))
|
||||
|
||||
|
||||
(defun org-ref-refproc-get-type (label referenceables)
|
||||
@@ -351,7 +351,7 @@ REFERENCEABLES comes from `org-ref-refproc-referenceables'.
|
||||
If CAPITALIZE is non-nil, capitalize the first entry (this is for
|
||||
Cref) and is different than the capitalize option in #+refproc:
|
||||
which capitalizes each prefix."
|
||||
(let* ((options (org-ref-refproc-get-options))
|
||||
(let* ((options (org-ref-refproc-get-options))
|
||||
(labels (split-string (org-element-property :path ref-link) ","))
|
||||
(post-blanks (org-element-property :post-blank ref-link))
|
||||
(data (cl-loop for label in labels collect (org-ref-refproc-get-type label referenceables)))
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
;; See http://dev.elsevier.com/index.html for more information about the Scopus API
|
||||
;;
|
||||
;; New org-links:
|
||||
;; eid:2-s2.0-72649092395 with a hydra menu
|
||||
;; eid:2-s2.0-72649092395 with a transient menu
|
||||
;; [[scopus-search:alloy Au segregation]]
|
||||
;; [[scopus-advanced-search:au-id(24176978500)]]
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'org)
|
||||
(require 'hydra)
|
||||
(require 'transient)
|
||||
(require 'xml)
|
||||
(require 'org-ref-utils)
|
||||
|
||||
@@ -142,28 +142,45 @@ Requires `*scopus-api-key*' to be defined."
|
||||
|
||||
|
||||
;;; Org-mode EID link and an action menu
|
||||
;; These functions use a global var *hydra-eid*
|
||||
(defvar *hydra-eid* nil
|
||||
"Global variable to pass an EID from an ‘org-mode’ link to a hydra function.")
|
||||
;; These functions use a global var to track the current EID.
|
||||
(defvar org-ref-scopus--current-eid nil
|
||||
"Global variable to pass an EID from an `org-mode' link to a transient function.")
|
||||
|
||||
(define-obsolete-variable-alias '*hydra-eid* 'org-ref-scopus--current-eid "3.1")
|
||||
|
||||
(defhydra scopus-hydra (:color blue)
|
||||
("o" (scopus-open-eid *hydra-eid*) "Open in Scopus")
|
||||
("a" (browse-url (format "http://www.scopus.com/search/submit/mlt.url?eid=%s&src=s&all=true&origin=recordpage&method=aut&zone=relatedDocuments" *hydra-eid*))
|
||||
"Related by author")
|
||||
("k" (browse-url (format "http://www.scopus.com/search/submit/mlt.url?eid=%s&src=s&all=true&origin=recordpage&method=key&zone=relatedDocuments" *hydra-eid*))
|
||||
"Related by keyword")
|
||||
("r" (browse-url (format "http://www.scopus.com/search/submit/mlt.url?eid=%s&src=s&all=true&origin=recordpage&method=ref&zone=relatedDocuments" *hydra-eid*))
|
||||
"Related by references")
|
||||
("c" (browse-url (format "http://www.scopus.com/results/citedbyresults.url?sort=plf-f&cite=%s&src=s&imp=t&sot=cite&sdt=a&sl=0&origin=recordpage" *hydra-eid*))
|
||||
"Citing articles"))
|
||||
(transient-define-prefix org-ref-scopus-menu ()
|
||||
"Scopus actions for the current EID."
|
||||
[["Open"
|
||||
("o" "Open in Scopus" (lambda () (interactive) (scopus-open-eid org-ref-scopus--current-eid)))]
|
||||
["Related"
|
||||
("a" "Related by author" (lambda () (interactive)
|
||||
(browse-url (format
|
||||
"http://www.scopus.com/search/submit/mlt.url?eid=%s&src=s&all=true&origin=recordpage&method=aut&zone=relatedDocuments"
|
||||
org-ref-scopus--current-eid))))
|
||||
("k" "Related by keyword" (lambda () (interactive)
|
||||
(browse-url (format
|
||||
"http://www.scopus.com/search/submit/mlt.url?eid=%s&src=s&all=true&origin=recordpage&method=key&zone=relatedDocuments"
|
||||
org-ref-scopus--current-eid))))
|
||||
("r" "Related by references" (lambda () (interactive)
|
||||
(browse-url (format
|
||||
"http://www.scopus.com/search/submit/mlt.url?eid=%s&src=s&all=true&origin=recordpage&method=ref&zone=relatedDocuments"
|
||||
org-ref-scopus--current-eid))))
|
||||
("c" "Citing articles" (lambda () (interactive)
|
||||
(browse-url (format
|
||||
"http://www.scopus.com/results/citedbyresults.url?sort=plf-f&cite=%s&src=s&imp=t&sot=cite&sdt=a&sl=0&origin=recordpage"
|
||||
org-ref-scopus--current-eid))))]
|
||||
["Quit"
|
||||
("q" "Quit" transient-quit-one)]])
|
||||
|
||||
(define-obsolete-function-alias 'scopus-hydra/body
|
||||
#'org-ref-scopus-menu "3.1")
|
||||
|
||||
|
||||
(org-link-set-parameters "eid"
|
||||
:follow (lambda (eid)
|
||||
"Opens the hydra menu."
|
||||
(setq *hydra-eid* eid)
|
||||
(scopus-hydra/body))
|
||||
"Opens the transient menu."
|
||||
(setq org-ref-scopus--current-eid eid)
|
||||
(org-ref-scopus-menu))
|
||||
:export (lambda (keyword desc format)
|
||||
(cond
|
||||
((eq format 'html)
|
||||
|
||||
@@ -44,15 +44,15 @@
|
||||
(defvar org-ref-cite-types)
|
||||
(defvar org-cliplink-escape-alist)
|
||||
|
||||
(declare-function 'org-ref-get-bibtex-key-and-file "org-ref-core.el")
|
||||
(declare-function 'org-ref-find-bibliography "org-ref-core.el")
|
||||
(declare-function org-ref-get-bibtex-key-and-file "org-ref-core")
|
||||
(declare-function org-ref-find-bibliography "org-ref-core")
|
||||
(declare-function org-ref-clean-bibtex-entry "org-ref-bibtex")
|
||||
|
||||
(declare-function dnd-handle-one-url "dnd")
|
||||
(declare-function dnd--unescape-uri "dnd")
|
||||
|
||||
(require 'doi-utils)
|
||||
(require 'f)
|
||||
(require 's)
|
||||
(require 'org-ref-utils)
|
||||
(eval-when-compile
|
||||
(require 'cl-lib))
|
||||
|
||||
@@ -82,7 +82,7 @@ the DOI."
|
||||
|
||||
|
||||
(defvar org-ref-url-title-re
|
||||
"<title.?+?>\\([[:ascii:][:nonascii:]]*?\\|.+\\)</title>"
|
||||
"<title[^>]*>\\(.*?\\)</title>"
|
||||
"Regular expression for matching title.")
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ the DOI."
|
||||
|
||||
|
||||
(defvar org-ref-url-date-re
|
||||
"<[a-z].+ class=\\(.?+date.[^>]*\\)>\\([[:ascii:][:nonascii:]]*?\\)</[a-z].+>"
|
||||
"<[a-z][^>]+ class=\\([^>]*date[^>]*\\)>\\(.*?\\)</[a-z][^>]+>"
|
||||
"Regular expression for matching date.")
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ there is more than one, we offer a completion buffer of selections. If
|
||||
no DOI is found, we create a misc entry, with a prompt for a key."
|
||||
;; make sure we are on a bib-file
|
||||
(if (and (buffer-file-name)
|
||||
(f-ext? (buffer-file-name) "bib"))
|
||||
(org-ref--file-ext-p (buffer-file-name) "bib"))
|
||||
(let ((dois (org-ref-url-scrape-dois url)))
|
||||
(cond
|
||||
;; One doi found. Assume it is what we want.
|
||||
@@ -172,7 +172,7 @@ no DOI is found, we create a misc entry, with a prompt for a key."
|
||||
"Protocol for when a doi is dragged onto a bibtex file.
|
||||
A doi will be either doi:10.xxx or 10.xxx."
|
||||
(if (and (buffer-file-name)
|
||||
(f-ext? (buffer-file-name) "bib"))
|
||||
(org-ref--file-ext-p (buffer-file-name) "bib"))
|
||||
(let ((doi (dnd--unescape-uri doi)))
|
||||
;; Get the actual doi now
|
||||
(string-match "\\(?:DOI\\|doi\\)?:? *\\(10.*\\)" doi)
|
||||
@@ -260,7 +260,7 @@ Fields include author, title, url, urldate, and year."
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward org-ref-url-title-re nil t)
|
||||
(push (cons :title
|
||||
(s-trim (decode-coding-string (match-string 1) 'utf-8)))
|
||||
(string-trim (decode-coding-string (match-string 1) 'utf-8)))
|
||||
fields)))
|
||||
|
||||
;; Finally add nil value to missing fields
|
||||
@@ -272,11 +272,11 @@ Fields include author, title, url, urldate, and year."
|
||||
"Convert URL to a bibtex or biblatex entry in BIBFILE.
|
||||
If URL is the first in the kill ring, use it. Otherwise, prompt for
|
||||
one in the minibuffer."
|
||||
(interactive (if (-contains? (org-ref-find-bibliography) (buffer-file-name))
|
||||
(interactive (if (member (buffer-file-name) (org-ref-find-bibliography))
|
||||
(list (buffer-file-name))
|
||||
(list (completing-read "Bibtex file: " (org-ref-find-bibliography)))))
|
||||
(let ((url (if url url
|
||||
(if (s-match "^http" (current-kill 0 'do-not-move))
|
||||
(if (org-ref--string-match "^http" (current-kill 0 'do-not-move))
|
||||
(format "%s" (current-kill 0 'do-not-move))
|
||||
(read-from-minibuffer "URL: ")))))
|
||||
(with-current-buffer
|
||||
@@ -284,12 +284,12 @@ one in the minibuffer."
|
||||
;; Maybe check dialect if set as local variable
|
||||
(let* ((dialect bibtex-dialect)
|
||||
(alist (org-ref-url-html-read url))
|
||||
(entry (s-format
|
||||
(entry (org-ref--format-template
|
||||
;; Check dialect and format entry accordingly
|
||||
(if (eq dialect 'biblatex)
|
||||
org-ref-url-biblatex-template
|
||||
org-ref-url-bibtex-template)
|
||||
'aget alist)))
|
||||
alist)))
|
||||
(goto-char (point-max))
|
||||
;; Place new entry one line after the last entry. Sometimes we are in a
|
||||
;; new file though, in which case we don't want to do this.
|
||||
|
||||
@@ -26,17 +26,193 @@
|
||||
|
||||
(require 'org)
|
||||
(eval-and-compile (require 'org-macs))
|
||||
(require 'subr-x) ; for string-trim and string-blank-p
|
||||
|
||||
|
||||
(defvar org-ref-cite-types)
|
||||
(defvar pdftotext-executable)
|
||||
|
||||
(declare-function 'org-ref-get-bibtex-key-and-file "org-ref-core.el")
|
||||
(declare-function 'org-ref-find-bibliography "org-ref-core.el")
|
||||
(declare-function 'org-ref-bib-citation "org-ref-core.el")
|
||||
(declare-function 'org-ref-get-bibtex-key-under-cursor "org-ref-core.el")
|
||||
(declare-function org-ref-get-bibtex-key-and-file "org-ref-core.el")
|
||||
(declare-function org-ref-find-bibliography "org-ref-core.el")
|
||||
(declare-function org-ref-bib-citation "org-ref-core.el")
|
||||
(declare-function org-ref-get-bibtex-key-under-cursor "org-ref-core.el")
|
||||
(declare-function org-ref-parse-cite-path "org-ref-citation-links.el")
|
||||
(declare-function org-ref-get-labels "org-ref-ref-links.el")
|
||||
|
||||
;; org-element functions (org.el)
|
||||
(declare-function org-element-parse-buffer "org-element")
|
||||
(declare-function org-element-map "org-element")
|
||||
(declare-function org-element-property "org-element")
|
||||
(declare-function org-mark-ring-push "org")
|
||||
|
||||
;; bibtex functions
|
||||
(declare-function bibtex-kill-entry "bibtex")
|
||||
(declare-function bibtex-validate "bibtex")
|
||||
(declare-function bibtex-global-key-alist "bibtex")
|
||||
(declare-function bibtex-autokey-get-field "bibtex")
|
||||
(declare-function bibtex-search-entry "bibtex")
|
||||
(declare-function bibtex-set-dialect "bibtex")
|
||||
(declare-function bibtex-copy-entry-as-kill "bibtex")
|
||||
(declare-function parsebib-find-bibtex-dialect "parsebib")
|
||||
|
||||
;; bibtex-completion functions
|
||||
(declare-function bibtex-completion-get-entry "bibtex-completion")
|
||||
(declare-function bibtex-completion-get-value "bibtex-completion")
|
||||
(declare-function bibtex-completion-show-entry "bibtex-completion")
|
||||
(declare-function bibtex-completion-edit-notes "bibtex-completion")
|
||||
(declare-function bibtex-completion-find-pdf "bibtex-completion")
|
||||
(declare-function bibtex-completion-find-pdf-in-library "bibtex-completion")
|
||||
(declare-function bibtex-completion-apa-format-reference "bibtex-completion")
|
||||
|
||||
;; doi-utils functions
|
||||
(declare-function doi-utils-ads "doi-utils")
|
||||
(declare-function doi-utils-wos "doi-utils")
|
||||
(declare-function doi-utils-wos-citing "doi-utils")
|
||||
(declare-function doi-utils-wos-related "doi-utils")
|
||||
(declare-function doi-utils-pubmed "doi-utils")
|
||||
(declare-function doi-utils-crossref "doi-utils")
|
||||
(declare-function doi-utils-get-bibtex-entry-pdf "doi-utils")
|
||||
|
||||
;; other functions
|
||||
(declare-function biblio-lookup "biblio")
|
||||
(declare-function message-goto-body "message")
|
||||
(declare-function message-goto-subject "message")
|
||||
(declare-function org-ref-bibtex-get-file-move-func "org-ref-bibtex")
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;** Dash.el replacement utilities
|
||||
|
||||
;; These functions replace dash.el dependencies with native Emacs equivalents.
|
||||
;; They use seq.el (Emacs 25+) and cl-lib (built-in).
|
||||
|
||||
(defun org-ref--flatten-list (list)
|
||||
"Flatten nested LIST structure.
|
||||
Compatible replacement for dash's `-flatten'.
|
||||
For Emacs 27+, consider using `flatten-tree' instead."
|
||||
(cond ((null list) nil)
|
||||
((listp list)
|
||||
(append (org-ref--flatten-list (car list))
|
||||
(org-ref--flatten-list (cdr list))))
|
||||
(t (list list))))
|
||||
|
||||
(defun org-ref--split-at (n list)
|
||||
"Split LIST at position N, returning (FIRST REST).
|
||||
Compatible replacement for dash's `-split-at'."
|
||||
(list (seq-take list n) (seq-drop list n)))
|
||||
|
||||
(defun org-ref--remove-at-indices (indices list)
|
||||
"Remove elements at INDICES from LIST.
|
||||
Compatible replacement for dash's `-remove-at-indices'."
|
||||
(cl-loop for item in list
|
||||
for i from 0
|
||||
unless (member i indices)
|
||||
collect item))
|
||||
|
||||
(defun org-ref--remove-at (index list)
|
||||
"Remove element at INDEX from LIST.
|
||||
Compatible replacement for dash's `-remove-at'."
|
||||
(append (seq-take list index)
|
||||
(seq-drop list (1+ index))))
|
||||
|
||||
(defun org-ref--insert-at (index element list)
|
||||
"Insert ELEMENT at INDEX in LIST.
|
||||
Compatible replacement for dash's `-insert-at'."
|
||||
(append (seq-take list index)
|
||||
(list element)
|
||||
(seq-drop list index)))
|
||||
|
||||
;;** S.el replacement utilities
|
||||
|
||||
;; These functions replace s.el dependencies with native Emacs equivalents.
|
||||
;; Most use built-in functions, some require subr-x (Emacs 24.4+).
|
||||
|
||||
(defun org-ref--format-template (template alist)
|
||||
"Format TEMPLATE string replacing ${key} with values from ALIST.
|
||||
ALIST should be an association list of (KEY . VALUE) pairs where
|
||||
KEY is a string. If a key in the template is not found in ALIST,
|
||||
it is replaced with an empty string.
|
||||
Compatible replacement for s-format with ${var} template syntax."
|
||||
(let ((result template))
|
||||
(dolist (pair alist)
|
||||
(let* ((key (car pair))
|
||||
(value (cdr pair))
|
||||
(pattern (format "${%s}" key))
|
||||
(replacement (if value (format "%s" value) "")))
|
||||
(setq result (string-replace pattern replacement result))))
|
||||
result))
|
||||
|
||||
(defun org-ref--string-match (regexp string &optional start)
|
||||
"Match REGEXP against STRING and return list of match groups.
|
||||
Returns a list where the first element is the entire match,
|
||||
and subsequent elements are the captured groups (subexpressions).
|
||||
Returns nil if no match is found.
|
||||
Optional START specifies where to start searching in STRING.
|
||||
Compatible replacement for s-match."
|
||||
(when (string-match regexp string start)
|
||||
(let ((result nil)
|
||||
(i 0))
|
||||
(while (match-beginning i)
|
||||
(push (match-string i string) result)
|
||||
(setq i (1+ i)))
|
||||
(nreverse result))))
|
||||
|
||||
;;** F.el replacement utilities
|
||||
|
||||
;; These functions replace f.el dependencies with native Emacs equivalents.
|
||||
;; Uses file-name-extension, directory-files, and file-name-concat (Emacs 28+).
|
||||
|
||||
(defun org-ref--file-ext-p (file ext)
|
||||
"Return t if FILE has extension EXT.
|
||||
EXT should not include the dot and comparison is case-insensitive.
|
||||
Compatible replacement for f-ext?."
|
||||
(when file
|
||||
(string= (downcase (or (file-name-extension file) ""))
|
||||
(downcase ext))))
|
||||
|
||||
(defun org-ref--directory-files (directory predicate)
|
||||
"Return files in DIRECTORY matching PREDICATE.
|
||||
PREDICATE is a function that takes a filename and returns non-nil
|
||||
if the file should be included. Excludes . and .. entries.
|
||||
Compatible replacement for f-entries."
|
||||
(when (file-directory-p directory)
|
||||
(seq-filter predicate
|
||||
(directory-files directory t "\\`[^.]"))))
|
||||
|
||||
(defun org-ref--file-join (&rest parts)
|
||||
"Join PARTS into a file path.
|
||||
Uses file-name-concat if available (Emacs 28+), otherwise uses
|
||||
expand-file-name for compatibility with older Emacs versions.
|
||||
Compatible replacement for f-join."
|
||||
(if (fboundp 'file-name-concat)
|
||||
(apply #'file-name-concat parts)
|
||||
;; Fallback for Emacs < 28
|
||||
(let ((path (car parts)))
|
||||
(dolist (part (cdr parts))
|
||||
(setq path (expand-file-name part path)))
|
||||
path)))
|
||||
|
||||
;;** bibtex-completion-bibliography utilities
|
||||
|
||||
(defvar bibtex-completion-bibliography)
|
||||
|
||||
(defun org-ref-normalize-bibtex-completion-bibliography ()
|
||||
"Return `bibtex-completion-bibliography' as a list of strings.
|
||||
This function handles three cases:
|
||||
1. If it's a function, call it and return the result
|
||||
2. If it's a list, return it as-is
|
||||
3. If it's a string, return it wrapped in a list
|
||||
|
||||
This ensures consistent handling across org-ref functions."
|
||||
(cond
|
||||
((functionp bibtex-completion-bibliography)
|
||||
(funcall bibtex-completion-bibliography))
|
||||
((listp bibtex-completion-bibliography)
|
||||
bibtex-completion-bibliography)
|
||||
(t
|
||||
(list bibtex-completion-bibliography))))
|
||||
|
||||
;;** org-ref functions
|
||||
;;;###autoload
|
||||
(defun org-ref-version ()
|
||||
"Provide a version string for org-ref.
|
||||
@@ -57,7 +233,7 @@ Copies the string to the clipboard."
|
||||
(goto-char (point-min))
|
||||
(if
|
||||
(re-search-forward ";; Version:" nil t)
|
||||
(s-trim (buffer-substring (point)
|
||||
(string-trim (buffer-substring (point)
|
||||
(line-end-position)))
|
||||
org-ref-dir)))
|
||||
|
||||
@@ -69,8 +245,8 @@ Copies the string to the clipboard."
|
||||
(or (file-directory-p ".git") (file-exists-p ".git"))
|
||||
(= 0 (shell-command "git rev-parse --git-dir")))
|
||||
(format "%s in %s"
|
||||
(s-trim (shell-command-to-string "git rev-parse HEAD"))
|
||||
(s-trim (shell-command-to-string "git rev-parse --show-toplevel"))))))
|
||||
(string-trim (shell-command-to-string "git rev-parse HEAD"))
|
||||
(string-trim (shell-command-to-string "git rev-parse --show-toplevel"))))))
|
||||
|
||||
(setq version-string (format "org-ref: Version %s%s"
|
||||
org-version
|
||||
@@ -106,7 +282,7 @@ Opens https://github.com/jkitchin/org-ref/issues/new."
|
||||
(erase-buffer)
|
||||
(org-mode)
|
||||
(insert
|
||||
(s-format "#+TITLE: org-ref debug
|
||||
(org-ref--format-template "#+TITLE: org-ref debug
|
||||
|
||||
${org-ref-version}
|
||||
|
||||
@@ -152,7 +328,6 @@ You set =pdftotext-executable= to ${pdftotext-executable} (exists: ${pdftotext-e
|
||||
|
||||
- org-latex-pdf-process :: ${org-latex-pdf-process}
|
||||
"
|
||||
'aget
|
||||
`(("org-ref-version" . ,(org-ref-version))
|
||||
("org-latex-pdf-process" . ,(format "%S" org-latex-pdf-process))
|
||||
("org-ref-location" . ,(format "%s" (locate-library "org-ref")))
|
||||
@@ -332,13 +507,13 @@ in a directory. Optional PREFIX argument toggles between
|
||||
;; I like this better than bibtex-url which does not always find
|
||||
;; the urls
|
||||
(catch 'done
|
||||
(let ((url (s-trim (bibtex-autokey-get-field "url"))))
|
||||
(unless (s-blank? url)
|
||||
(let ((url (string-trim (bibtex-autokey-get-field "url"))))
|
||||
(unless (string-blank-p url)
|
||||
(browse-url url)
|
||||
(throw 'done nil)))
|
||||
|
||||
(let ((doi (s-trim (bibtex-autokey-get-field "doi"))))
|
||||
(unless (s-blank? doi)
|
||||
(let ((doi (string-trim (bibtex-autokey-get-field "doi"))))
|
||||
(unless (string-blank-p doi)
|
||||
(if (string-match "^http" doi)
|
||||
(browse-url doi)
|
||||
(browse-url (format "http://dx.doi.org/%s" doi)))
|
||||
@@ -594,8 +769,14 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
(lambda (x)
|
||||
(file-name-directory (file-truename x)))
|
||||
bibtex-files ":"))
|
||||
(bibtex-keys (mapcar (lambda (x) (car x))
|
||||
(bibtex-global-key-alist)))
|
||||
(bibtex-key-alist (bibtex-global-key-alist))
|
||||
;; Handle both old alist format and new completion table format
|
||||
(bibtex-keys (if (functionp bibtex-key-alist)
|
||||
;; New format: completion table (function)
|
||||
;; Call with nil to get all completions
|
||||
(all-completions "" bibtex-key-alist)
|
||||
;; Old format: alist
|
||||
(mapcar (lambda (x) (car x)) bibtex-key-alist)))
|
||||
(bad-citations '()))
|
||||
|
||||
(org-element-map (org-ref-parse-buffer) 'link
|
||||
@@ -629,7 +810,7 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
(when (assoc (plist-get plist ':type) org-ref-ref-types)
|
||||
(cl-loop for label in (split-string (plist-get plist :path) ",")
|
||||
do
|
||||
(unless (-contains? labels label)
|
||||
(unless (member label labels)
|
||||
(goto-char (org-element-property :begin link))
|
||||
(add-to-list
|
||||
'bad-refs
|
||||
@@ -647,7 +828,7 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward rx nil t)
|
||||
(cl-pushnew (match-string-no-properties 1) labels))))
|
||||
(-count (lambda (x) (and (stringp x) (string= x label))) labels)))
|
||||
(cl-count-if (lambda (x) (and (stringp x) (string= x label))) labels)))
|
||||
|
||||
|
||||
(defun org-ref-bad-label-candidates ()
|
||||
@@ -877,7 +1058,7 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
bib-candidates)))))
|
||||
|
||||
;; check for multiple bibliography links
|
||||
(let* ((bib-links (-filter
|
||||
(let* ((bib-links (seq-filter
|
||||
(lambda (el)
|
||||
(string= (org-element-property :type el) "bibliography"))
|
||||
(org-element-map (org-element-parse-buffer) 'link 'identity)))
|
||||
@@ -931,7 +1112,7 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^@\\(.*?\\)[({]" nil t)
|
||||
(when (and (not (string= "string" (downcase (match-string-no-properties 1))))
|
||||
(not (member (s-trim (downcase (match-string-no-properties 1)))
|
||||
(not (member (string-trim (downcase (match-string-no-properties 1)))
|
||||
(cdr (assoc bibtex-dialect
|
||||
(list
|
||||
(cons 'BibTeX (mapcar (lambda (e) (downcase (car e)))
|
||||
@@ -1008,7 +1189,7 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
(org-element-property :path el))))))
|
||||
(cl-loop for (label . p) in matches
|
||||
do
|
||||
(when (and label (not (-contains? refs label)))
|
||||
(when (and label (not (member label refs)))
|
||||
(cl-pushnew
|
||||
(cons label (set-marker (make-marker) p))
|
||||
unreferenced-labels)))))))
|
||||
@@ -1160,9 +1341,9 @@ if FORCE is non-nil reparse the buffer no matter what."
|
||||
|
||||
(insert "\n* Warnings\n")
|
||||
(if (get-buffer "*Warnings*")
|
||||
(cl-loop for line in (s-split "\n" (with-current-buffer "*Warnings*"
|
||||
(cl-loop for line in (split-string "\n" (with-current-buffer "*Warnings*"
|
||||
(buffer-string)))
|
||||
if (s-starts-with? "Warning (org-ref):" line)
|
||||
if (string-prefix-p "Warning (org-ref):" line)
|
||||
do
|
||||
(insert " - " line "\n"))
|
||||
(insert "- No (org-ref) Warnings found."))
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
;;; and an org-mode link for a link to an Accession number.
|
||||
|
||||
(require 'org)
|
||||
(require 's)
|
||||
(require 'org-ref-utils)
|
||||
|
||||
;;; Code:
|
||||
@@ -45,13 +44,13 @@
|
||||
:follow (lambda (path)
|
||||
(browse-url
|
||||
(format "http://gateway.webofknowledge.com/gateway/Gateway.cgi?topic=%s&GWVersion=2&SrcApp=WEB&SrcAuth=HSB&DestApp=UA&DestLinkType=GeneralSearchSummary"
|
||||
(s-join "+" (split-string path)))))
|
||||
(string-join (split-string path) "+"))))
|
||||
:export (lambda (link desc format)
|
||||
(cond
|
||||
((eq format 'html)
|
||||
(format "<a href=\"%s\">%s</a>"
|
||||
(format "http://gateway.webofknowledge.com/gateway/Gateway.cgi?topic=%s&GWVersion=2&SrcApp=WEB&SrcAuth=HSB&DestApp=UA&DestLinkType=GeneralSearchSummary"
|
||||
(s-join "+" (split-string link)))
|
||||
(string-join (split-string link) "+"))
|
||||
(or desc link))))))
|
||||
|
||||
;;;###autoload
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
|
||||
;; URL: https://github.com/jkitchin/org-ref
|
||||
;; Package-Version: 20250301.1918
|
||||
;; Package-Revision: edbb80863ef6
|
||||
;; Package-Version: 20251123.1710
|
||||
;; Package-Revision: 5275e58bca7e
|
||||
;; Keywords: org-mode, cite, ref, label
|
||||
;; Package-Requires: ((org "9.4") (dash "0") (s "0") (f "0") (htmlize "0") (hydra "0") (avy "0") (parsebib "0") (bibtex-completion "0") (citeproc "0") (ox-pandoc "0") (request "0"))
|
||||
;; Package-Requires: ((org "9.4") (htmlize "0") (transient "0") (avy "0") (parsebib "0") (bibtex-completion "0") (citeproc "0") (ox-pandoc "0") (request "0"))
|
||||
;; This file is not currently part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or
|
||||
@@ -45,7 +45,7 @@
|
||||
org-ref-insert-cite-function 'org-ref-insert-cite-link
|
||||
org-ref-insert-label-function 'org-ref-insert-label-link
|
||||
org-ref-insert-ref-function 'org-ref-insert-ref-link
|
||||
org-ref-cite-onclick-function (lambda (_) (org-ref-citation-hydra/body)))
|
||||
org-ref-cite-onclick-function (lambda (_) (org-ref-citation-menu)))
|
||||
|
||||
|
||||
;;* The end
|
||||
|
||||
@@ -143,9 +143,9 @@ Here is the setup I use.
|
||||
bibtex-autokey-titlewords 2
|
||||
bibtex-autokey-titlewords-stretch 1
|
||||
bibtex-autokey-titleword-length 5)
|
||||
(define-key bibtex-mode-map (kbd "H-b") 'org-ref-bibtex-hydra/body)
|
||||
(define-key bibtex-mode-map (kbd "H-b") 'org-ref-bibtex-entry-menu)
|
||||
(define-key org-mode-map (kbd "C-c ]") 'org-ref-insert-link)
|
||||
(define-key org-mode-map (kbd "s-[") 'org-ref-insert-link-hydra/body)
|
||||
(define-key org-mode-map (kbd "s-[") 'org-ref-insert-link-menu)
|
||||
(require 'org-ref-ivy)
|
||||
(require 'org-ref-arxiv)
|
||||
(require 'org-ref-scopus)
|
||||
@@ -155,11 +155,11 @@ Here is the setup I use.
|
||||
(use-package org-ref-ivy
|
||||
:ensure nil
|
||||
:load-path (lambda () (expand-file-name "org-ref" scimax-dir))
|
||||
:init (setq org-ref-insert-link-function 'org-ref-insert-link-hydra/body
|
||||
:init (setq org-ref-insert-link-function 'org-ref-insert-link-menu
|
||||
org-ref-insert-cite-function 'org-ref-cite-insert-ivy
|
||||
org-ref-insert-label-function 'org-ref-insert-label-link
|
||||
org-ref-insert-ref-function 'org-ref-insert-ref-link
|
||||
org-ref-cite-onclick-function (lambda (_) (org-ref-citation-hydra/body))))
|
||||
org-ref-cite-onclick-function (lambda (_) (org-ref-citation-menu))))
|
||||
#+END_SRC
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ org-ref uses the [[bibliography link]] to determine which bibtex files to get ci
|
||||
|
||||
For simple citation needs, org-ref is simple to use. At the point you want to insert a citation, you select the "Org -> org-ref -> Insert citation" menu (or use the key-binding you chose for inserting org-ref links), select the reference(s) you want in the completion buffer and press enter. The citation will be inserted automatically into your org-file. You "select" an entry by using the arrow keys (or ~C-n~ and ~C-p~) to move up and down to the entry you want. You can also narrow the selection by typing a pattern to match, e.g. author name, title words, year, BibTeX key and entry types. If you want to match any other field, you need to add it to the variable ~bibtex-completion-additional-search-fields~.
|
||||
|
||||
If the cursor is on a citation key, you should see a tooltip that summarizes which citation it refers to. If you click on a key, you should get a hydra menu with some actions to choose, including opening the bibtex entry, opening/getting a pdf for the entry, searching the entry in Web of Science, etc...
|
||||
If the cursor is on a citation key, you should see a tooltip that summarizes which citation it refers to. If you click on a key, you should get a transient menu with some actions to choose, including opening the bibtex entry, opening/getting a pdf for the entry, searching the entry in Web of Science, etc...
|
||||
|
||||
The default citation type is customizable (=org-ref-default-citation-link=), and set to "cite".
|
||||
|
||||
@@ -283,7 +283,7 @@ You can also refer to an org-ref label link as in Table ref:tab-ydata.
|
||||
|
||||
To help you insert ref links, use the "Org -> org-ref -> Insert ref" menu, or run the command = org-ref-insert-ref-link=.
|
||||
|
||||
ref links are functional. If you put the cursor on a ref link, you will see a tooltip with some context of the corresponding label. If you click on the ref link, you will get a hydra to jump to it or change the ref type.
|
||||
ref links are functional. If you put the cursor on a ref link, you will see a tooltip with some context of the corresponding label. If you click on the ref link, you will open a transient menu to jump to it or change the ref type.
|
||||
|
||||
You can make a ref link to a section with a CUSTOM_ID. For this to work, you should set ~org-latex-prefer-user-labels~ to t.
|
||||
|
||||
@@ -695,22 +695,22 @@ These functions are compatible with ~bibtex-map-entries~, so it is possible to c
|
||||
- org-ref-bibtex-next-entry :: bound to M-n
|
||||
- org-ref-bibtex-previous-entry :: bound to M-p
|
||||
|
||||
*** Hydra menus for bibtex entries and files
|
||||
*** Transient menus for bibtex entries and files
|
||||
|
||||
- Functions to act on a bibtex entry or file
|
||||
- org-ref-bibtex-hydra/body :: gives a hydra menu to a lot of useful functions
|
||||
- org-ref-bibtex-entry-menu :: opens a transient menu with many useful functions
|
||||
like opening the pdf, or the entry in a browser, or searching in a
|
||||
variety of scientific search engines.
|
||||
- org-ref-bibtex-new-entry/body :: gives a hydra menu to add new bibtex entries.
|
||||
- org-ref-bibtex-file/body :: gives a hydra menu of actions for the bibtex file.
|
||||
- org-ref-bibtex-new-entry-menu :: opens a transient menu to add new bibtex entries.
|
||||
- org-ref-bibtex-file-menu :: opens a transient menu of actions for the bibtex file.
|
||||
|
||||
You will want to bind the hydra menus to a key. You only need to bind the first one, as the second and third can be accessed from the first hydra.
|
||||
You will want to bind the transient menus to a key. You only need to bind the first one, as the second and third can be accessed from the first menu.
|
||||
|
||||
*** Formatted bibtex entry citations
|
||||
|
||||
If you click on a citation link, there should be a menu option to copy a formatted citation, which will copy the citation string to the clipboard. This is a lightly formatted version using bibtex-completion.
|
||||
|
||||
If you are on a bibtex entry, the `org-ref-bibtex-hydra/body' has an option to copy a formatted citation for the entry your cursor is in.
|
||||
If you are on a bibtex entry, the `org-ref-bibtex-entry-menu' has an option to copy a formatted citation for the entry your cursor is in.
|
||||
|
||||
|
||||
** org-ref-wos
|
||||
@@ -749,7 +749,7 @@ Open a basic search in Scopus: [[scopus-search:alloy Au segregation]]
|
||||
|
||||
Open an advanced search in Scopus: scopus-advanced-search:au-id(7004212771). See http://www.scopus.com/search/form.url?display=advanced&clear=t for details on the codes and syntax, and http://help.elsevier.com/app/answers/detail/a_id/2347/p/8150/incidents.c$portal_account_name/26389.
|
||||
|
||||
A functional link to a document in Scopus by its "EID": eid:2-s2.0-72649092395. Clicking on this link will open a hydra menu to open the document in Scopus, find different kinds of related documents by keywords, authors or references, and to open a page in Scopus of citing documents.
|
||||
A functional link to a document in Scopus by its "EID": eid:2-s2.0-72649092395. Clicking on this link will open a transient menu to open the document in Scopus, find different kinds of related documents by keywords, authors or references, and to open a page in Scopus of citing documents.
|
||||
|
||||
There is also a scopusid link for authors that will open their author page in Scopus: scopusid:7004212771
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
;;; Code:
|
||||
(require 'bibtex)
|
||||
|
||||
(declare-function org-ref-clean-bibtex-entry "org-ref-bibtex" ())
|
||||
|
||||
|
||||
;;* RIS to bibtex
|
||||
|
||||
|
||||
Reference in New Issue
Block a user