update packages

This commit is contained in:
2025-11-25 19:52:03 +01:00
parent 14ba373378
commit dbbae92267
280 changed files with 13451 additions and 11207 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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<>\"\"")))

View File

@@ -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)

View File

@@ -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'")

View File

@@ -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))))))

View File

@@ -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))

View File

@@ -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

View File

@@ -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)

View File

@@ -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)))

View File

@@ -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

View File

@@ -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},

View File

@@ -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))))

View File

@@ -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")))

View File

@@ -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)

View File

@@ -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))

View File

@@ -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

View File

@@ -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)))

View File

@@ -2,7 +2,7 @@
'((dash "0")
(s "0")
(f "0")
(hydra "0")
(transient "0")
(htmlize "0")
(parsebib "0")
(bibtex-completion "0")

View File

@@ -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")))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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?

View File

@@ -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)))

View File

@@ -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)

View File

@@ -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.

View File

@@ -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."))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -42,6 +42,8 @@
;;; Code:
(require 'bibtex)
(declare-function org-ref-clean-bibtex-entry "org-ref-bibtex" ())
;;* RIS to bibtex