123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- ;;; org-noter.el --- A synchronized, Org-mode, document annotator -*- lexical-binding: t; -*-
- ;; Copyright (C) 2017-2018 Gonçalo Santos
- ;; Author: Gonçalo Santos (aka. weirdNox@GitHub)
- ;; Homepage: https://github.com/weirdNox/org-noter
- ;; Keywords: lisp pdf interleave annotate external sync notes documents org-mode
- ;; Package-Requires: ((emacs "24.4") (cl-lib "0.6") (org "9.0"))
- ;; Version: 1.4.1
- ;; This file is not part of GNU Emacs.
- ;; 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:
- ;; The idea is to let you create notes that are kept in sync when you scroll through the
- ;; document, but that are external to it - the notes themselves live in an Org-mode file. As
- ;; such, this leverages the power of Org-mode (the notes may have outlines, latex fragments,
- ;; babel, etc...) while acting like notes that are made /in/ the document.
- ;; Also, I must thank Sebastian for the original idea and inspiration!
- ;; Link to the original Interleave package:
- ;; https://github.com/rudolfochrist/interleave
- ;;; Code:
- (require 'org-element)
- (require 'cl-lib)
- (require 'org-noter-core)
- (declare-function org-entry-put "org")
- (declare-function org-with-wide-buffer "org-macs")
- ;;;###autoload
- (defun org-noter (&optional arg)
- "Start `org-noter' session.
- There are two modes of operation. You may create the session from:
- - The Org notes file
- - The document to be annotated (PDF, EPUB, ...)
- - Creating the session from notes file -----------------------------------------
- This will open a session for taking your notes, with indirect
- buffers to the document and the notes side by side. Your current
- window configuration won't be changed, because this opens in a
- new frame.
- You only need to run this command inside a heading (which will
- hold the notes for this document). If no document path property is found,
- this command will ask you for the target file.
- With a prefix universal argument ARG, only check for the property
- in the current heading, don't inherit from parents.
- With 2 prefix universal arguments ARG, ask for a new document,
- even if the current heading annotates one.
- With a prefix number ARG:
- - Greater than 0: Open the document like `find-file'
- - Equal to 0: Create session with `org-noter-always-create-frame' toggled
- - Less than 0: Open the folder containing the document
- - Creating the session from the document ---------------------------------------
- This will try to find a notes file in any of the parent folders.
- The names it will search for are defined in `org-noter-default-notes-file-names'.
- It will also try to find a notes file with the same name as the
- document, giving it the maximum priority.
- When it doesn't find anything, it will interactively ask you what
- you want it to do. The target notes file must be in a parent
- folder (direct or otherwise) of the document.
- You may pass a prefix ARG in order to make it let you choose the
- notes file, even if it finds one."
- (interactive "P")
- (cond
- ;; NOTE(nox): Creating the session from notes file
- ((eq major-mode 'org-mode)
- (let* ((notes-file-path (buffer-file-name))
- (document-property (org-noter--get-or-read-document-property
- (not (equal arg '(4)))
- (equal arg '(16))))
- (org-noter-always-create-frame
- (if (and (numberp arg) (= arg 0))
- (not org-noter-always-create-frame)
- org-noter-always-create-frame))
- (ast (org-noter--parse-root (vector (current-buffer) document-property)))
- (session-id (get-text-property (org-element-property :begin ast) org-noter--id-text-property))
- session)
- ;; Check for prefix value
- (if (or (numberp arg) (eq arg '-))
- ;; Yes, user's given a prefix value.
- (cond ((> (prefix-numeric-value arg) 0)
- ;; Is the prefix value greater than 0?
- (find-file document-property))
- ;; Open the document like `find-file'.
- ;; Is the prefix value less than 0?
- ((< (prefix-numeric-value arg) 0)
- ;; Open the folder containing the document.
- (find-file (file-name-directory document-property))))
- ;; No, user didn't give a prefix value
- ;; NOTE(nox): Check if it is an existing session
- (when session-id
- (setq session (cl-loop for session in org-noter--sessions
- when (= (org-noter--session-id session) session-id)
- return session))))
- (if session
- (let* ((org-noter--session session)
- (location (org-noter--parse-location-property
- (org-noter--get-containing-element))))
- (org-noter--setup-windows session)
- (when location (org-noter--doc-goto-location location))
- (select-frame-set-input-focus (org-noter--session-frame session)))
- ;; It's not an existing session, create a new session.
- (org-noter--create-session ast document-property notes-file-path))))
- ;; NOTE(nox): Creating the session from the annotated document
- ((memq major-mode org-noter-supported-modes)
- (if (org-noter--valid-session org-noter--session)
- (progn (org-noter--setup-windows org-noter--session)
- (select-frame-set-input-focus (org-noter--session-frame org-noter--session)))
- ;; NOTE(nox): `buffer-file-truename' is a workaround for modes that delete
- ;; `buffer-file-name', and may not have the same results
- (let* ((buffer-file-name (or (run-hook-with-args-until-success 'org-noter-get-buffer-file-name-hook major-mode)
- buffer-file-name))
- (document-path (or buffer-file-name buffer-file-truename
- (error "This buffer does not seem to be visiting any file")))
- (document-name (file-name-nondirectory document-path))
- (document-base (file-name-base document-name))
- (document-directory (if buffer-file-name
- (file-name-directory buffer-file-name)
- (if (file-equal-p document-name buffer-file-truename)
- default-directory
- (file-name-directory buffer-file-truename))))
- ;; NOTE(nox): This is the path that is actually going to be used, and should
- ;; be the same as `buffer-file-name', but is needed for the truename workaround
- (document-used-path (expand-file-name document-name document-directory))
- (search-names (append org-noter-default-notes-file-names
- (list (concat document-base ".org"))
- (list (run-hook-with-args-until-success 'org-noter-find-additional-notes-functions document-path))))
- notes-files-annotating ; List of files annotating document
- notes-files ; List of found notes files (annotating or not)
- (document-location (org-noter--doc-approx-location)))
- ;; NOTE(nox): Check the search path
- (dolist (path org-noter-notes-search-path)
- (dolist (name search-names)
- (let ((file-name (expand-file-name name path)))
- (when (file-exists-p file-name)
- (push file-name notes-files)
- (when (org-noter--check-if-document-is-annotated-on-file document-path file-name)
- (push file-name notes-files-annotating))))))
- ;; NOTE(nox): `search-names' is in reverse order, so we only need to (push ...)
- ;; and it will end up in the correct order
- (dolist (name search-names)
- (let ((directory (locate-dominating-file document-directory name))
- file)
- (when directory
- (setq file (expand-file-name name directory))
- (unless (member file notes-files) (push file notes-files))
- (when (org-noter--check-if-document-is-annotated-on-file document-path file)
- (push file notes-files-annotating)))))
- (setq search-names (nreverse search-names))
- (when (or arg (not notes-files-annotating))
- (when (or arg (not notes-files))
- (let* ((notes-file-name (completing-read "What name do you want the notes to have? "
- search-names nil t))
- list-of-possible-targets
- target)
- ;; NOTE(nox): Create list of targets from current path
- (catch 'break
- (let ((current-directory document-directory)
- file-name)
- (while t
- (setq file-name (expand-file-name notes-file-name current-directory))
- (when (file-exists-p file-name)
- (setq file-name (propertize file-name 'display
- (concat file-name
- (propertize " -- Exists!" 'face '(:foregorund "green")))))
- (push file-name list-of-possible-targets)
- (throw 'break nil))
- (push file-name list-of-possible-targets)
- (when (string= current-directory
- (setq current-directory
- (file-name-directory (directory-file-name current-directory))))
- (throw 'break nil)))))
- (setq list-of-possible-targets (nreverse list-of-possible-targets))
- ;; NOTE(nox): Create list of targets from search path
- (dolist (path org-noter-notes-search-path)
- (when (file-exists-p path)
- (let ((file-name (expand-file-name notes-file-name path)))
- (unless (member file-name list-of-possible-targets)
- (when (file-exists-p file-name)
- (setq file-name (propertize file-name 'display
- (concat file-name
- (propertize " -- Exists!" 'face '(:foreground "green"))))))
- (push file-name list-of-possible-targets)))))
- (setq target (completing-read "Where do you want to save it? " list-of-possible-targets
- nil t))
- (set-text-properties 0 (length target) nil target)
- (unless (file-exists-p target) (write-region "" nil target))
- (setq notes-files (list target))))
- (when (> (length notes-files) 1)
- (setq notes-files (list (completing-read "In which notes file should we create the heading? "
- notes-files nil t))))
- (if (member (car notes-files) notes-files-annotating)
- ;; NOTE(nox): This is needed in order to override with the arg
- (setq notes-files-annotating notes-files)
- (with-current-buffer (find-file-noselect (car notes-files))
- (goto-char (point-max))
- (insert (if (save-excursion (beginning-of-line) (looking-at "[[:space:]]*$")) "" "\n")
- "* " document-base)
- (org-entry-put nil org-noter-property-doc-file
- (file-relative-name document-used-path
- (file-name-directory (car notes-files)))))
- (setq notes-files-annotating notes-files)))
- (when (> (length (delete-dups notes-files-annotating)) 1)
- (setq notes-files-annotating (list (completing-read "Which notes file should we open? "
- notes-files-annotating nil t))))
- (with-current-buffer (find-file-noselect (car notes-files-annotating))
- (org-with-point-at (point-min)
- (catch 'break
- (while (re-search-forward (org-re-property org-noter-property-doc-file) nil t)
- (when (file-equal-p (expand-file-name (match-string 3)
- (file-name-directory (car notes-files-annotating)))
- document-path)
- (let ((org-noter--start-location-override document-location))
- (org-noter arg))
- (throw 'break t)))))))))))
- (provide 'org-noter)
- ;;; org-noter.el ends here
|