Skip to content

Instantly share code, notes, and snippets.

@fuxialexander
Last active April 6, 2023 11:48
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save fuxialexander/e9111f1f291aa6075950e08fdfa6eb13 to your computer and use it in GitHub Desktop.
Save fuxialexander/e9111f1f291aa6075950e08fdfa6eb13 to your computer and use it in GitHub Desktop.
Integration between Org-ref and Skim.app [Skim Annotation <-> Org Bidirectional Jump]
(setq org-capture-templates (quote (("SA" "Skim Annotation" entry
(file+function org-ref-bibliography-notes my-org-move-point-to-capture-skim-annotation)
"* %^{Logging for...} :skim_annotation:read:literature:
:PROPERTIES:
:Created: %U
:CITE: cite:%(my-as-get-skim-bibtex-key)
:SKIM_NOTE: %(my-org-mac-skim-get-page)
:SKIM_PAGE: %(my-as-get-skim-page)
:END:
%i
%?" :prepend f :empty-lines 1))
(with-eval-after-load 'org-ref-ivy
(org-link-set-parameters "skim" :follow #'my-org-mac-skim-open)
(defun my-org-mac-skim-open (uri)
"Visit page of pdf in Skim"
(let* ((note (when (string-match ";;\\(.+\\)\\'" uri) (match-string 1 uri)))
(page (when (string-match "::\\(.+\\);;" uri) (match-string 1 uri)))
(document (substring uri 0 (match-beginning 0))))
(do-applescript
(concat
"tell application \"Skim\"\n"
"activate\n"
"set theDoc to \"" document "\"\n"
"set thepage to " page "\n"
"set theNote to " note "\n"
"open theDoc\n"
"go document 1 to page thePage of document 1\n"
"if theNote is not 0\n"
" go document 1 to item theNote of notes of page thePage of document 1\n"
" set active note of document 1 to item theNote of notes of page thePage of document 1\n"
"end if\n"
"end tell"))))
(defadvice org-capture-finalize
(after org-capture-finalize-after activate)
"Advise capture-finalize to close the frame"
(if (equal "SA" (org-capture-get :key))
(do-applescript "tell application \"Skim\"\n activate\nend tell")))
(defun my-as-get-skim-page-link ()
(do-applescript
(concat
"tell application \"Skim\"\n"
"set theDoc to front document\n"
"set theTitle to (name of theDoc)\n"
"set thePath to (path of theDoc)\n"
"set thePage to (get index for current page of theDoc)\n"
"set theSelection to selection of theDoc\n"
"set theContent to (contents of (get text for theSelection))\n"
"try\n"
" set theNote to active note of theDoc\n"
"end try\n"
"if theNote is not missing value then\n"
" set theContent to contents of (get text for theNote)\n"
" set theNotePage to get page of theNote\n"
" set thePage to (get index for theNotePage)\n"
" set theNoteIndex to (get index for theNote on theNotePage)\n"
"else\n"
" if theContent is missing value then\n"
" set theContent to theTitle & \", p. \" & thePage\n"
" set theNoteIndex to 0\n"
" else\n"
" tell theDoc\n"
" set theNote to make new note with data theSelection with properties {type:highlight note}\n"
" set active note of theDoc to theNote\n"
" set text of theNote to (get text for theSelection)\n"
" set theNotePage to get page of theNote\n"
" set thePage to (get index for theNotePage)\n"
" set theNoteIndex to (get index for theNote on theNotePage)\n"
" set theContent to contents of (get text for theNote)\n"
" end tell\n"
" end if\n"
"end if\n"
"set theLink to \"skim://\" & thePath & \"::\" & thePage & \";;\" & theNoteIndex & "
"\"::split::\" & theContent\n"
"end tell\n"
"return theLink as string\n")))
(defun my-as-get-skim-bibtex-key ()
(let* ((name (do-applescript
(concat
"tell application \"Skim\"\n"
"set theDoc to front document\n"
"set theTitle to (name of theDoc)\n"
"end tell\n"
"return theTitle as string\n")))
(key (when (string-match "\"\\(.+\\).pdf\"" name) (match-string 1 name))))
key)
)
(defun my-as-get-skim-page ()
(let* ((page (do-applescript
(concat
"tell application \"Skim\"\n"
"set theDoc to front document\n"
"set thePage to (get index for current page of theDoc)\n"
"end tell\n"
"return thePage as integer\n"))))
page))
(defun my-as-clean-skim-page-link (link)
(let* ((link (replace-regexp-in-string "\n" " " link))
(link (replace-regexp-in-string "- " " " link)))
link))
(defun my-org-mac-skim-get-page ()
(interactive)
(message "Applescript: Getting Skim page link...")
(org-mac-paste-applescript-links (my-as-clean-skim-page-link (my-as-get-skim-page-link))))
(defun my-org-mac-skim-insert-page ()
(interactive)
(insert (my-org-mac-skim-get-page)))
(defun my-org-move-point-to-capture ()
(cond ((org-at-heading-p) (org-beginning-of-line))
(t (org-previous-visible-heading 1))))
(defun my-org-ref-find-entry-in-notes (key)
"Find or create bib note for KEY"
(let* ((entry (bibtex-completion-get-entry key)))
(widen)
(goto-char (point-min))
(unless (derived-mode-p 'org-mode)
(error
"Target buffer \"%s\" for jww/find-journal-tree should be in Org mode"
(current-buffer)))
(let* ((headlines (org-element-map
(org-element-parse-buffer)
'headline 'identity))
(keys (mapcar
(lambda (hl) (org-element-property :CUSTOM_ID hl))
headlines)))
;; put new entry in notes if we don't find it.
(if (-contains? keys key)
(progn
(org-open-link-from-string (format "[[#%s]]" key))
(lambda nil
(cond ((org-at-heading-p)
(org-beginning-of-line))
(t (org-previous-visible-heading 1))))
)
;; no entry found, so add one
(goto-char (point-max))
(insert (org-ref-reftex-format-citation
entry (concat "\n" org-ref-note-title-format)))
(mapc (lambda (x)
(save-restriction
(save-excursion
(funcall x))))
org-ref-create-notes-hook)
(org-open-link-from-string (format "[[#%s]]" key))
(lambda nil
(cond ((org-at-heading-p)
(org-beginning-of-line))
(t (org-previous-visible-heading 1))))
))
))
(defun my-org-move-point-to-capture-skim-annotation ()
(let* ((keystring (my-as-get-skim-bibtex-key)))
(my-org-ref-find-entry-in-notes keystring)
))
)
(add-hook 'org-capture-prepare-finalize-hook #'(lambda () (my-as-set-skim-org-link (org-id-get-create))))
(defun my-as-set-skim-org-link (id)
(do-applescript (concat
"tell application \"Skim\"\n"
"set runstatus to \"not set\"\n"
"set theDoc to front document\n"
"try\n"
" set theNote to active note of theDoc\n"
"end try\n"
"if theNote is not missing value then\n"
" set newText to text of theNote\n"
" set startpoint to (offset of \"org-id:{\" in newtext)\n"
" set endpoint to (offset of \"}:org-id\" in newtext)\n"
" if (startpoint is equal to endpoint) and (endpoint is equal to 0) then\n"
" set newText to text of theNote & \"\norg-id:{\" & "
(applescript-quote-string id)
" & \"}:org-id\"\n"
" set text of theNote to newText\n"
" return \"set success\"\n"
" end if\n"
"end if\n"
"end tell\n"
"return \"unset\"\n"
)))
(defun applescript-quote-string (argument)
"Quote a string for passing as a string to AppleScript."
(if (or (not argument) (string-equal argument ""))
"\"\""
;; Quote using double quotes, but escape any existing quotes or
;; backslashes in the argument with backslashes.
(let ((result "")
(start 0)
end)
(save-match-data
(if (or (null (string-match "[^\"\\]" argument))
(< (match-end 0) (length argument)))
(while (string-match "[\"\\]" argument start)
(setq end (match-beginning 0)
result (concat result (substring argument start end)
"\\" (substring argument end (1+ end)))
start (1+ end))))
(concat "\"" result (substring argument start) "\"")))))
@fuxialexander
Copy link
Author

To use it, just bound
/usr/local/bin/emacsclient -n -e '(progn (x-focus-frame (selected-frame)) (org-capture nil "SA"))'
to a shortcut

@fuxialexander
Copy link
Author

fuxialexander commented Dec 21, 2017

Added (my-as-clean-skim-page-link link) to remove \n and - in link.

@fuxialexander
Copy link
Author

Added bi-direction jump through Org-id. This need (require 'org-id) and a UUID will be generated for each captured annotation item (I use ID for all my headings actually). After id-generation ((org-id-get-create)), the id is passed to Skim annotation through Applescript and recorded in text of annotation.

In order to jump from Skim annotation to corresponding Org heading, you can use this Applescript. I bound it to "three finger double tap" using BetterTouchTool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment