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