org-zotxt.el 9.31 KB
Newer Older
Erik Hetzner committed
1
;;; org-zotxt.el --- Interface org-mode with Zotero via the zotxt extension
Erik Hetzner committed
2 3

;; Copyright (C) 2010-2016 Erik Hetzner
Erik Hetzner committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

;; Author: Erik Hetzner <egh@e6h.org>
;; Keywords: bib

;; This file is not part of GNU Emacs.

;; org-zotxt.el 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.

;; org-zotxt.el 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 org-zotxt.el. If not, see
;; <http://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:

28 29
(eval-when-compile
  (require 'cl))
30
(require 'request-deferred)
31
(require 'org-element)
Erik Hetzner committed
32
(require 'zotxt)
Erik Hetzner committed
33

34
(defcustom org-zotxt-link-description-style
35 36 37 38
  :citation
  "Style to use for org zotxt link texts."
  :group 'org-zotxt
  :type '(choice (const :tag "easykey" :easykey)
39
                 (const :tag "better BibTeX" :betterbibtexkey)
40 41
                 (const :tag "citation" :citation)))

42 43 44 45 46 47 48 49 50 51 52 53
(defcustom org-zotxt-default-search-method nil
  "Default method to use for searching with `org-zotxt-insert-reference-link'.
If nil, the user is prompted to choose each time.

A selected default method can be bypassed by giving a double
prefix argument (C-u C-u) to `org-zotxt-insert-reference-link'"
  :group 'org-zotxt
  :type (append '(choice) '((const :tag "Choose each time" nil))
                (mapcar
                 (lambda (c) (list 'const :tag (car c) (cdr c)))
                 zotxt-quicksearch-method-names)))

54 55 56 57
(defun org-zotxt-extract-link-id-at-point ()
  "Extract the Zotero key of the link at point."
  (let ((ct (org-element-context)))
    (if (eq 'link (org-element-type ct))
58
        (org-zotxt-extract-link-id-from-path (org-element-property :path ct))
59 60
      nil)))

61
(defun org-zotxt-extract-link-id-from-path (path)
62
  "Return the zotxt ID from a link PATH."
63 64
  (if (string-match "^\\(zotero:\\)?//select/items/\\(.*\\)$" path)
      (match-string 2 path)
65 66
    nil))

67
(defun org-zotxt-insert-reference-link-to-item (item)
68
  "Insert link to Zotero ITEM in buffer."
69 70
  (insert (org-make-link-string (format "zotero://select/items/%s"
                                        (plist-get item :key))
71 72 73
                                (if (or (eq org-zotxt-link-description-style :easykey)
                                        (eq org-zotxt-link-description-style :betterbibtexkey))
                                    (concat "@" (plist-get item org-zotxt-link-description-style))
74
                                  (plist-get item :citation)))))
75

76 77 78 79 80 81 82 83
(defun org-zotxt-insert-reference-links-to-items (items)
  "Insert links to Zotero ITEMS in buffer."
  (mapc (lambda (item)
          (org-zotxt-insert-reference-link-to-item item)
          (insert "\n")
          (forward-line 1))
        items))

Erik Hetzner committed
84
(defun org-zotxt-update-reference-link-at-point ()
85
  "Update the zotero:// link at point."
Erik Hetzner committed
86
  (interactive)
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
  (lexical-let ((mk (point-marker))
                (item-id (org-zotxt-extract-link-id-at-point)))
    (if item-id
        (deferred:$
          (deferred:next (lambda () `(:key ,item-id)))
          (deferred:nextc it
            (lambda (item)
              (org-zotxt-get-item-link-text-deferred item)))
          (deferred:nextc it
            (lambda (item)
              (save-excursion
                (with-current-buffer (marker-buffer mk)
                  (goto-char (marker-position mk))
                  (let ((ct (org-element-context)))
                    (goto-char (org-element-property :begin ct))
                    (delete-region (org-element-property :begin ct)
                                   (org-element-property :end ct))
104 105
                    (org-zotxt-insert-reference-link-to-item item))))))
          (if zotxt--debug-sync (deferred:sync! it))))))
Erik Hetzner committed
106

Erik Hetzner committed
107
(defun org-zotxt-update-all-reference-links ()
Erik Hetzner committed
108
  "Update all zotero:// links in a document."
109 110
  (interactive)
  (save-excursion
111 112 113 114 115 116 117 118
    (widen)
    (goto-char (point-max))
    (while (re-search-backward org-any-link-re nil t)
      (let* ((parse (org-element-link-parser))
             (path (org-element-property :path parse)))
        (when (org-zotxt-extract-link-id-from-path path)
          (message "[zotxt] updating path: %s" path)
          (org-zotxt-update-reference-link-at-point))))))
119

120 121 122
(defun org-zotxt-get-item-link-text-deferred (item)
  "Get the link text for ITEM.
May be either an easy key or bibliography, depending on the value
123 124 125 126
of `org-zotxt-link-description-style'."
  (if (or (eq org-zotxt-link-description-style :easykey)
          (eq org-zotxt-link-description-style :betterbibtexkey))
      (zotxt-get-item-deferred item org-zotxt-link-description-style)
127 128
    (zotxt-get-item-bibliography-deferred item)))

Erik Hetzner committed
129 130 131 132 133 134
(defun org-zotxt-insert-reference-link (&optional arg)
  "Insert a zotero link in the `org-mode' document.

Prompts for search to choose item.  If prefix argument ARG is used,
will insert the currently selected item from Zotero.  If double
prefix argument is used the search method will have to be
135
selected even if `org-zotxt-default-search-method' is non-nil"
136
  (interactive "P")
137
  (lexical-let ((mk (point-marker)))
138
    (deferred:$
Erik Hetzner committed
139
      (if (equal '(4) arg)
140
          (zotxt-get-selected-items-deferred)
141
        (zotxt-choose-deferred (unless (equal '(16) arg) org-zotxt-default-search-method)))
142 143
      (deferred:nextc it
        (lambda (items)
144 145 146
          (if (null items)
              (error "No item found for search")
            (zotxt-mapcar-deferred #'org-zotxt-get-item-link-text-deferred items))))
147 148 149 150
      (deferred:nextc it
        (lambda (items)
          (with-current-buffer (marker-buffer mk)
            (goto-char (marker-position mk))
151 152 153
            (org-zotxt-insert-reference-links-to-items items))))
      (deferred:error it
        (lambda (err)
154 155
          (error (error-message-string err))))
      (if zotxt--debug-sync (deferred:sync! it)))))
156

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
(defun org-zotxt--link-follow (path)
  "Function used for zotero links to follow the link to PATH."
  (zotxt-select-key (substring path 15)))

(defun org-zotxt--link-export (path desc format)
  "Function used for zotero links to export the link.

PATH is the path of the link, the text after the prefix (like \"http:\")
DESC is the description of the link, if any
FORMAT is the export format, a symbol like ‘html’ or ‘latex’ or ‘ascii’."
  (if (string-match "^@\\(.*\\)$" desc)
      (pcase format
        (`latex (format "\\cite{%s}"
                        ;; hack to replace all the escaping that latex
                        ;; gives us in the desc with _
                        (replace-regexp-in-string
                         "\\([{}]\\|\\\\text\\|\\\\(\\|\\\\)\\)" ""
                         (match-string 1 desc))))
        (`md desc)
        (_ nil))))

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
(defvar org-zotxt--links-defined nil)

;; We need to support org 9 and org 8, but this code will generate compiler
;; warnings without this
(with-no-warnings
  (defun org-zotxt--define-links ()
    "Set up the links for zotxt."
    (when (not org-zotxt--links-defined)
      (setq org-zotxt--links-defined t)
      (if (functionp #'org-link-set-parameters)
          (org-link-set-parameters "zotero"
                                   :follow #'org-zotxt--link-follow
                                   :export #'org-zotxt--link-export)
        (org-add-link-type "zotero"
                           #'org-zotxt--link-follow
                           #'org-zotxt--link-export)))))
Erik Hetzner committed
194

Erik Hetzner committed
195
(defvar org-zotxt-mode-map
Erik Hetzner committed
196
  (let ((map (make-sparse-keymap)))
197
    (define-key map (kbd "C-c \" i") 'org-zotxt-insert-reference-link)
198
    (define-key map (kbd "C-c \" u") 'org-zotxt-update-reference-link-at-point)
199
    (define-key map (kbd "C-c \" a") 'org-zotxt-open-attachment)
Erik Hetzner committed
200 201
    map))

202 203 204 205 206 207 208 209
(defun org-zotxt-choose-path (paths)
  "Prompt user to select a path from the PATHS.
If only path is available, return it.  If no paths are available, error."
  (if (= 0 (length paths))
      (progn (message "No attachments for item!")
             (error "No attachments for item!"))
    (if (= 1 (length paths))
        (elt paths 0)
210
      (completing-read "File: " (append paths nil)))))
211

Erik Hetzner committed
212
(defun org-zotxt-open-attachment (&optional arg)
213
  "Open attachment of Zotero items linked at point.
Erik Hetzner committed
214 215

Opens with `org-open-file', see for more information about ARG."
216
  (interactive "P")
217 218
  (lexical-let ((item-id (org-zotxt-extract-link-id-at-point))
                (arg arg))
219
    (deferred:$
220
      (request-deferred
221
       (format "%s/items" zotxt-url-base)
222
       :params `(("key" . ,item-id) ("format" . "paths"))
223
       :parser 'json-read)
224 225 226
      (deferred:nextc it
        (lambda (response)
          (let ((paths (cdr (assq 'paths (elt (request-response-data response) 0)))))
227 228
            (org-open-file (org-zotxt-choose-path paths) arg))))
      (if zotxt--debug-sync (deferred:sync! it)))))
229

230
;;;###autoload
Erik Hetzner committed
231 232
(define-minor-mode org-zotxt-mode
  "Toggle org-zotxt-mode.
Erik Hetzner committed
233 234 235 236 237
With no argument, this command toggles the mode.
Non-null prefix argument turns on the mode.
Null prefix argument turns off the mode.

This is a minor mode for managing your citations with Zotero in a
Erik Hetzner committed
238
org-mode document."
Erik Hetzner committed
239
  nil
240
  " OrgZot"
241 242
  org-zotxt-mode-map
  (org-zotxt--define-links))
Erik Hetzner committed
243

Erik Hetzner committed
244
(provide 'org-zotxt)
Erik Hetzner committed
245
;;; org-zotxt.el ends here