Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

562 lines
18 KiB

;;; org-ref-ivy-cite.el --- Use ivy for completion in org-ref -*- lexical-binding: t; -*-
;; Copyright (C) 2016 John Kitchin
;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(declare-function 'org-ref-insert-key-at-point "org-ref-core.el")
(declare-function 'org-ref-find-bibliography "org-ref-core.el")
(declare-function 'org-ref-get-labels "org-ref-core.el")
(declare-function 'org-ref-get-bibtex-key-and-file "org-ref-core.el")
;;; Code:
(require 'ivy)
(require 'org-ref-bibtex)
(require 'org-ref-citeproc)
(require 'bibtex-completion)
;; This lets you customize how the completion for ivy is displayed. The default
;; is in the minibuffer. You may like to see something more like a popup though.
(defcustom org-ref-ivy-display-function nil
"ivy function to display completion with.
Set to `ivy-display-function-overlay' to get popups at point."
:type 'function
:group 'org-ref)
(when org-ref-ivy-display-function
(add-to-list 'ivy-display-functions-alist
`(org-ref-ivy-insert-cite-link . ,org-ref-ivy-display-function))
(add-to-list 'ivy-display-functions-alist
`(org-ref-ivy-insert-label-link . ,org-ref-ivy-display-function))
(add-to-list 'ivy-display-functions-alist
`(org-ref-ivy-insert-ref-link . ,org-ref-ivy-display-function)))
(defvar org-ref-cite-types)
(defvar org-ref-show-citation-on-enter)
(defvar org-ref-ivy-cite-marked-candidates '()
"Holds entries marked in `org-ref-ivy-insert-cite-link'.")
;;;###autoload
(defun org-ref-ivy-cite-completion ()
"Use ivy for completion."
(interactive)
;; Define core functions for org-ref
(setq org-ref-insert-link-function 'org-ref-insert-link
org-ref-insert-cite-function 'org-ref-ivy-insert-cite-link
org-ref-insert-label-function 'org-ref-ivy-insert-label-link
org-ref-insert-ref-function 'org-ref-ivy-insert-ref-link
org-ref-cite-onclick-function (lambda (_) (org-ref-cite-hydra/body))))
(org-ref-ivy-cite-completion)
(define-key org-mode-map
(kbd org-ref-insert-cite-key)
org-ref-insert-link-function)
(defun or-looking-forward-cite ()
"Return if point is in the position before a citation."
(save-excursion
(forward-char)
(-contains? org-ref-cite-types
(org-element-property
:type
(org-element-context)))))
(defun or-looking-back-cite ()
"Return if point is in the position after a citation."
(save-excursion
(forward-char -1)
(-contains? org-ref-cite-types
(org-element-property
:type
(org-element-context)))))
(defun or-ivy-bibtex-insert-cite (entry)
"Insert a citation for ENTRY.
If `org-ref-ivy-cite-marked-candidates' is non-nil then they are added instead of ENTRY.
ENTRY is selected from `orhc-bibtex-candidates'."
(with-ivy-window
(if org-ref-ivy-cite-marked-candidates
(cl-loop for entry in org-ref-ivy-cite-marked-candidates
do
(if ivy-current-prefix-arg
(let ((org-ref-default-citation-link (ivy-read "Type: " org-ref-cite-types)))
(org-ref-insert-key-at-point (list (cdr (assoc "=key=" entry)))))
(org-ref-insert-key-at-point (list (cdr (assoc "=key=" entry))))))
(if ivy-current-prefix-arg
(let ((org-ref-default-citation-link (ivy-read "Type: " org-ref-cite-types)))
(org-ref-insert-key-at-point (list (cdr (assoc "=key=" entry)))))
(org-ref-insert-key-at-point (list (cdr (assoc "=key=" entry))))))))
(defun or-ivy-bibtex-open-pdf (entry)
"Open the pdf associated with ENTRY.
ENTRY is selected from `orhc-bibtex-candidates'."
(with-ivy-window
(let ((pdf (expand-file-name
(format "%s.pdf"
(cdr (assoc "=key=" entry)))
org-ref-pdf-directory)))
(if (file-exists-p pdf)
(org-open-file pdf)
(message "No pdf found for %s" (cdr (assoc "=key=" entry)))))))
(defun or-ivy-bibtex-open-notes (entry)
"Open the notes associated with ENTRY.
ENTRY is selected from `orhc-bibtex-candidates'."
(with-ivy-window
(org-ref-open-notes-at-point
(cdr (assoc "=key=" entry)))))
(defun or-ivy-bibtex-open-entry (entry)
"Open the bibtex file at ENTRY.
ENTRY is selected from `orhc-bibtex-candidates'."
(find-file (cdr (assoc "bibfile" entry)))
(goto-char (cdr (assoc "position" entry)))
(bibtex-beginning-of-entry))
(defun or-ivy-bibtex-copy-entry (entry)
"Copy selected bibtex ENTRY to the clipboard."
(with-temp-buffer
(save-window-excursion
(or-ivy-bibtex-open-entry entry)
(bibtex-copy-entry-as-kill))
(bibtex-yank)
(kill-region (point-min) (point-max))))
(defun or-ivy-bibtex-open-url (entry)
"Open the URL associated with ENTRY.
ENTRY is selected from `orhc-bibtex-candidates'."
(let ((url (cdr (assoc "url" entry))))
(if url
(browse-url url)
(message "No url found for %s" (cdr (assoc "=key=" entry))))))
(defun or-ivy-bibtex-open-doi (entry)
"Open the DOI associated with ENTRY.
ENTRY is selected from `orhc-bibtex-candidates'."
(let ((doi (cdr (assoc "doi" entry))))
(if doi
(browse-url (format "http://dx.doi.org/%s" doi))
(message "No doi found for %s" (cdr (assoc "=key=" entry))))))
(defun or-ivy-bibtex-set-keywords (entry)
"Prompt for keywords, and put them on the selected ENTRY."
(let ((keywords (read-string "Keyword(s) comma-separated: " ))
entry-keywords)
(save-window-excursion
(or-ivy-bibtex-open-entry entry)
(setq entry-keywords (bibtex-autokey-get-field "keywords"))
(bibtex-set-field
"keywords"
(if (> (length entry-keywords) 0)
(concat entry-keywords ", " keywords)
keywords)))))
(defun or-ivy-bibtex-email-entry (entry)
"Insert selected ENTRY and attach pdf file to an email.
Create email unless called from an email."
(with-ivy-window
(let ((goto-to nil))
(unless (memq major-mode '(message-mode mu4e-compose-mode))
(setq goto-to t)
(compose-mail)
(message-goto-body))
(save-window-excursion
(or-ivy-bibtex-open-entry entry)
(bibtex-copy-entry-as-kill))
(insert (pop bibtex-entry-kill-ring))
(insert "\n")
(let ((pdf (expand-file-name
(format "%s.pdf"
(cdr (assoc "=key=" entry)))
org-ref-pdf-directory)))
(if (file-exists-p pdf)
(mml-attach-file pdf)))
(when goto-to
(message-goto-to)))))
(defun or-ivy-bibtex-formatted-citation (entry)
"Return string containing formatted citations for ENTRY.
This uses a citeproc library."
(let ((enable-recursive-minibuffers t))
(ivy-read "Style: " '("unsrt" "author-year")
:action 'load-library
:require-match t
:preselect "unsrt"
:caller 'or-ivy-formatted-citation)
(format "%s\n\n" (orhc-formatted-citation entry))))
(defun or-ivy-bibtex-insert-formatted-citation (entry)
"Insert formatted citations at point for selected entries."
(with-ivy-window
(insert (mapconcat
'identity
(cl-loop for entry in (or org-ref-ivy-cite-marked-candidates (list entry))
collect (org-ref-format-bibtex-entry entry))
"\n\n"))))
(defun or-ivy-bibtex-copy-formatted-citation (entry)
"Copy formatted citation to clipboard for ENTRY."
(kill-new (org-ref-format-entry entry)))
(defun or-ivy-bibtex-add-entry (_)
"Open a bibliography file and move point to the end, in order
to add a new bibtex entry. The arg is selected from
`orhc-bibtex-candidates' but ignored."
(ivy-read "bibtex file: " org-ref-bibtex-files
:require-match t
:action 'find-file
:caller 'or-ivy-bibtex-add-entry)
(widen)
(goto-char (point-max))
(unless (bolp)
(insert "\n")))
(defvar org-ref-ivy-cite-actions
'(("b" or-ivy-bibtex-open-entry "Open bibtex entry")
("B" or-ivy-bibtex-copy-entry "Copy bibtex entry")
("p" or-ivy-bibtex-open-pdf "Open pdf")
("n" or-ivy-bibtex-open-notes "Open notes")
("u" or-ivy-bibtex-open-url "Open url")
("d" or-ivy-bibtex-open-doi "Open doi")
("k" or-ivy-bibtex-set-keywords "Add keywords")
("e" or-ivy-bibtex-email-entry "Email entry")
("f" or-ivy-bibtex-insert-formatted-citation "Insert formatted citation")
("F" or-ivy-bibtex-copy-formatted-citation "Copy formatted citation")
("a" or-ivy-bibtex-add-entry "Add bibtex entry"))
"List of additional actions for `org-ref-ivy-insert-cite-link' (the default action being to insert a citation).")
(defvar org-ref-ivy-cite-re-builder 'ivy--regex-ignore-order
"Regex builder to use in `org-ref-ivy-insert-cite-link'. Can be set to nil to use Ivy's default).")
(defun org-ref-swap (i j lst)
"Swap index I and J in the list LST."
(let ((tempi (nth i lst)))
(setf (nth i lst) (nth j lst))
(setf (nth j lst) tempi))
lst)
(defun org-ref-ivy-current ()
(if (boundp 'ivy--current)
ivy--current
(ivy-state-current ivy-last)))
(defun org-ref-ivy-move-up ()
"Move ivy candidate up and update candidates."
(interactive)
(setf (ivy-state-collection ivy-last)
(org-ref-swap ivy--index (1- ivy--index) (ivy-state-collection ivy-last)))
(setf (ivy-state-preselect ivy-last) (org-ref-ivy-current))
(ivy--reset-state ivy-last))
(defun org-ref-ivy-move-down ()
"Move ivy candidate down."
(interactive)
(setf (ivy-state-collection ivy-last)
(org-ref-swap ivy--index (1+ ivy--index) (ivy-state-collection ivy-last)))
(setf (ivy-state-preselect ivy-last) (org-ref-ivy-current))
(ivy--reset-state ivy-last))
(defun org-ref-ivy-sort-year-ascending ()
"Sort entries by year in ascending order."
(interactive)
(setf (ivy-state-collection ivy-last)
(cl-sort (copy-sequence (ivy-state-collection ivy-last))
(lambda (a b)
(let ((y1 (string-to-number (or (cdr (assoc "year" a)) "0")))
(y2 (string-to-number (or (cdr (assoc "year" b)) "0"))))
(< y1 y2)))))
(setf (ivy-state-preselect ivy-last) (org-ref-ivy-current))
(ivy--reset-state ivy-last))
(defun org-ref-ivy-sort-year-descending ()
"sort entries by year in descending order."
(interactive)
(setf (ivy-state-collection ivy-last)
(cl-sort (copy-sequence (ivy-state-collection ivy-last))
(lambda (a b)
(let ((y1 (string-to-number (or (cdr (assoc "year" a)) "0")))
(y2 (string-to-number (or (cdr (assoc "year" b)) "0"))))
(> y1 y2)))))
(setf (ivy-state-preselect ivy-last) (org-ref-ivy-current))
(ivy--reset-state ivy-last))
;; * marking candidates
(defun org-ref-ivy-mark-candidate ()
"Add current candidate to `org-ref-ivy-cite-marked-candidates'.
If candidate is already in, remove it."
(interactive)
(let ((cand (or (assoc (org-ref-ivy-current) (ivy-state-collection ivy-last))
(org-ref-ivy-current))))
(if (-contains? org-ref-ivy-cite-marked-candidates cand)
;; remove it from the marked list
(setq org-ref-ivy-cite-marked-candidates
(-remove-item cand org-ref-ivy-cite-marked-candidates))
;; add to list
(setq org-ref-ivy-cite-marked-candidates
(append org-ref-ivy-cite-marked-candidates (list cand)))))
(ivy-next-line))
(defun org-ref-ivy-show-marked-candidates ()
"Show marked candidates."
(interactive)
(setf (ivy-state-collection ivy-last) org-ref-ivy-cite-marked-candidates)
(setf (ivy-state-preselect ivy-last) (org-ref-ivy-current))
(ivy--reset-state ivy-last))
(defun org-ref-ivy-show-all ()
"Show all the candidates."
(interactive)
(setf (ivy-state-collection ivy-last)
(orhc-bibtex-candidates))
(ivy--reset-state ivy-last))
;; * org-ref-cite keymap
(defvar org-ref-ivy-cite-keymap
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-<SPC>") 'org-ref-ivy-mark-candidate)
(define-key map (kbd "C-,") 'org-ref-ivy-show-marked-candidates)
(define-key map (kbd "C-.") 'org-ref-ivy-show-all)
(define-key map (kbd "C-<up>") 'org-ref-ivy-move-up)
(define-key map (kbd "C-<down>") 'org-ref-ivy-move-down)
(define-key map (kbd "C-y") 'org-ref-ivy-sort-year-ascending)
(define-key map (kbd "C-M-y") 'org-ref-ivy-sort-year-descending)
(define-key map (kbd "C-k") (lambda ()
(interactive)
(beginning-of-line)
(kill-visual-line)
(setf (ivy-state-collection ivy-last)
(orhc-bibtex-candidates))
(setf (ivy-state-preselect ivy-last) (org-ref-ivy-current))
(ivy--reset-state ivy-last)))
(define-key map (kbd "C-<return>")
(lambda ()
"Apply action and move to next/previous candidate."
(interactive)
(ivy-call)
(ivy-next-line)))
;; (define-key ivy-minibuffer-map (kbd "M-<return>")
;; (lambda ()
;; "Apply default action to all marked candidates."
;; (interactive)
;; (mapc (ivy--get-action ivy-last)
;; org-ref-ivy-cite-marked-candidates)
;; (ivy-exit-with-action (function (lambda (_) nil)))))
map)
"A key map for `org-ref-ivy-insert-cite-link'.")
(ivy-set-actions
'org-ref-ivy-insert-cite-link
org-ref-ivy-cite-actions)
(defun org-ref-ivy-insert-cite-link (&optional arg)
"ivy function for interacting with bibtex.
Uses `org-ref-find-bibliography' for bibtex sources, unless a
prefix ARG is used, which uses `org-ref-default-bibliography'."
(interactive "P")
(setq org-ref-bibtex-files (if arg
org-ref-default-bibliography
(org-ref-find-bibliography)))
(setq org-ref-ivy-cite-marked-candidates '())
(ivy-read "Open: " (orhc-bibtex-candidates)
:require-match t
:keymap org-ref-ivy-cite-keymap
:re-builder org-ref-ivy-cite-re-builder
:action 'or-ivy-bibtex-insert-cite
:caller 'org-ref-ivy-insert-cite-link))
(defun org-ref-ivy-cite-transformer (s)
"Make entry red if it is marked."
(let* ((fill-column (frame-width))
(fill-prefix " ")
(wrapped-s (with-temp-buffer
(insert s)
(fill-paragraph)
(buffer-string))))
(if (-contains?
(if (listp (car org-ref-ivy-cite-marked-candidates))
(mapcar 'car org-ref-ivy-cite-marked-candidates)
org-ref-ivy-cite-marked-candidates)
s)
(propertize wrapped-s 'face 'font-lock-warning-face)
(propertize wrapped-s 'face nil))))
(ivy-set-display-transformer
'org-ref-ivy-insert-cite-link
'org-ref-ivy-cite-transformer )
(defun org-ref-ivy-insert-label-link ()
"Insert a label with ivy."
(interactive)
(insert
(concat (if (not (looking-back "label:" 6)) "label:" "")
(ivy-read "label: " (org-ref-get-labels)
:caller 'org-ref-ivy-insert-label-link))))
(defun org-ref-ivy-insert-ref-link ()
"Insert a ref link with ivy.
Use a prefix arg to select the ref type."
(interactive)
(let ((label (ivy-read "label: " (org-ref-get-labels) :require-match t
:caller 'org-ref-ivy-insert-ref-link)))
(cond
;; from a colon insert
((looking-back ":" 1)
(insert label))
;; non-default
(ivy-current-prefix-arg
(insert
(ivy-read "type: " org-ref-ref-types)
":"
label))
;; default
(t
(insert
(or (when (looking-at "$") " ") "")
(concat org-ref-default-ref-type
":"
label))))))
(require 'hydra)
(setq hydra-is-helpful t)
(defhydra org-ref-cite-hydra (:color blue)
"
_p_: Open pdf _w_: WOS _g_: Google Scholar _K_: Copy citation to clipboard
_u_: Open url _r_: WOS related _P_: Pubmed _k_: Copy key to clipboard
_n_: Open notes _c_: WOS citing _C_: Crossref _f_: Copy formatted entry
_o_: Open entry _e_: Email entry ^ ^ _q_: quit
_i_: Insert cite _h_: change type
"
("o" org-ref-open-citation-at-point nil)
("p" org-ref-open-pdf-at-point nil)
("n" org-ref-open-notes-at-point nil)
("u" org-ref-open-url-at-point nil)
("w" org-ref-wos-at-point nil)
("r" org-ref-wos-related-at-point nil)
("c" org-ref-wos-citing-at-point nil)
("g" org-ref-google-scholar-at-point nil)
("P" org-ref-pubmed-at-point nil)
("C" org-ref-crossref-at-point nil)
("K" org-ref-copy-entry-as-summary nil)
("k" (progn
(kill-new
(car (org-ref-get-bibtex-key-and-file))))
nil)
("f" (kill-new
(org-ref-format-entry (org-ref-get-bibtex-key-under-cursor)))
nil)
("e" (kill-new (save-excursion
(org-ref-open-citation-at-point)
(org-ref-email-bibtex-entry)))
nil)
("i" (funcall org-ref-insert-cite-function))
("h" org-ref-change-cite-type)
("q" nil))
(defun org-ref-ivy-onclick-actions ()
"An alternate click function that uses ivy for action selection.
Each action is taken from `org-ref-ivy-cite-actions'. Each action
should act on a bibtex entry that matches the key in
`orhc-bibtex-candidates'. Set `org-ref-cite-onclick-function' to
this function to use it."
(interactive)
(ivy-read
"action: "
(cl-loop for i from 0
for (_ func s) in
org-ref-ivy-cite-actions
collect (cons (format "%2s. %s" i s) func))
:action (lambda (f)
(let* ((key (car (org-ref-get-bibtex-key-and-file)))
(entry (cdr (elt (orhc-bibtex-candidates)
(-elem-index
key
(cl-loop for entry in (orhc-bibtex-candidates)
collect (cdr (assoc "=key=" entry ))))))))
(funcall f entry)))))
;; * org-ref-ivy-set-keywords
(defvar org-ref-ivy-set-keywords-keymap
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-<SPC>") 'org-ref-ivy-mark-candidate)
(define-key map (kbd "C-,") 'org-ref-ivy-show-marked-candidates)
(define-key map (kbd "C-.") 'org-ref-ivy-show-all)
(define-key map (kbd "C-<up>") 'org-ref-ivy-move-up)
(define-key map (kbd "C-<down>") 'org-ref-ivy-move-down)
map)
"A key map for `org-ref-ivy-set-keywords'.")
(defun org-ref-ivy-set-keywords ()
"Add keywords to bibtex entries selected by org-ref-ivy."
(interactive)
(setq org-ref-ivy-cite-marked-candidates '())
(ivy-read "Keywords: " (org-ref-bibtex-keywords)
:keymap org-ref-ivy-set-keywords-keymap
:caller 'org-ref-ivy-set-keywords
:action (lambda (key)
(org-ref-set-bibtex-keywords
(mapconcat
'identity
(or org-ref-ivy-cite-marked-candidates (list key))
", ")))))
(ivy-set-display-transformer
'org-ref-ivy-set-keywords
'org-ref-ivy-cite-transformer)
(provide 'org-ref-ivy-cite)
;;; org-ref-ivy-cite.el ends here