).
+
+ It will be one of these two:
+ - =org-noter--get-containing-heading=
+ - =org-noter--check-location-property=
+ found bug: [[file:org-noter-core.el:1023]] change test from integerp to numberp
+ - =org-noter--get-containing-property-drawer=
+** Custom variables
+ Presently, the custom variables listed under =customize-group org-noter= is a
+ flat list. I would like to group them into logical categories.
+
+*** start-stop
+ - org-noter-supported-modes '(doc-view-mode pdf-view-mode nov-mode djvu-read-mode)
+ - org-noter-auto-save-last-location nil
+ - org-noter-default-notes-file-names '("Notes.org")
+ - org-noter-notes-search-path '("~/Documents")
+ - org-noter-notes-window-behavior '(start scroll)
+ - org-noter-suggest-from-attachments t
+ - org-noter-find-additional-notes-functions nil
+ - org-noter-always-create-frame t
+ - org-noter-kill-frame-at-session-end t
+ - org-noter-use-indirect-buffer t
+
+*** layout
+ - org-noter-notes-window-location 'horizontal-split
+ - org-noter-doc-split-fraction '(0.5 . 0.5)
+ - org-noter-disable-narrowing nil
+ - org-noter-swap-window nil
+ - org-noter-hide-other t
+
+*** note-insertion
+ - org-noter-default-heading-title "Notes for page $p$"
+ - org-noter-separate-notes-from-heading nil
+ - org-noter-insert-selected-text-inside-note t
+ - org-noter-highlight-selected-text nil
+ - org-noter-max-short-selected-text-length 80
+ - org-noter-insert-heading-hook nil
+ - org-noter-insert-note-no-questions nil
+
+*** navigation-display
+ - org-noter-arrow-delay 0.2
+ - org-noter-arrow-horizontal-offset -0.02
+ - org-noter-arrow-foreground-color "orange red"
+ - org-noter-arrow-background-color "white"
+ - org-noter-closest-tipping-point 0.3
+ - org-noter-no-notes-exist-face
+ - org-noter-notes-exist-face
+
+*** other
+ - org-noter-property-doc-file "NOTER_DOCUMENT"
+ - org-noter-property-note-location "NOTER_PAGE"
+ - org-noter-prefer-root-as-file-level nil # used in org-noter--parse-root
+ - org-noter-doc-property-in-notes nil
diff --git a/docs/org-noter-demo.org b/docs/org-noter-demo.org
new file mode 100644
index 0000000..fc3ad90
--- /dev/null
+++ b/docs/org-noter-demo.org
@@ -0,0 +1,70 @@
+* Opening a notes session
+ - open PDF
+ - ~M-x org-noter~
+ - ~M-x org-noter-set-doc-split-fraction~
+ - ~M-x org-noter-set-notes-window-location, M-n~ (org-noter-set-layout?) side-by-side | stacked
+ - ~M-x org-noter-create-skeleton, M-n~
+
+* Navigating the document and the notes
+ - ~SPC, n, Page-down, down~ to move forward in document
+ - ~BACKSPACE, p, Page-up, up~ to move back in document
+ - ~C-M-n, C-M-p~ to move to next/prev note
+ - ~C-M-.~ sync document to notes
+ - ~M-n, M-p~ to move to next/prev page with note(s). Always lands on the
+ first note of the page.
+ - ~M-.~ sync notes to document
+
+* Multicolumn setup (as needed)
+ - Multiple column note ordering can be set up at the document or heading level
+ - ~org-noter-pdf-set-columns~ inserts "COLUMN_EDGES" into the property drawer
+ of the current heading. The command requests the number of columns and then
+ asks you to click on the right edge of all but the last column. The
+ property is inherited by all sub-headings.
+
+* Note insertion
+
+** ~insert-note~ (~i~)
+ - Inserts a note linked to the current page. If no title is specified, then
+ default title "Notes for page " is used, where
is the pagelabel if
+ it exists or the page number.
+ - If text is selected AND it is "short" (see ~defcustom
+ org-noter-max-short-selected-text-length~) the the selected text becomes
+ the default title.
+ - If you type in a title, then the selected text is quoted in the body of the
+ note. Short selected text is set in ``LaTeX-style quotes,''
+ #+begin_quote
+ while long selected text is set inside QUOTE block delimiters.
+ #+end_quote
+ - At the title prompt =Note:=, you can use ~M-p~ to "up-arrow" prior note
+ headings, or ~M-n~ to select from the defaults.
+ - If you choose a prior note heading, then selected text will be quoted in
+ that heading.
+
+** ~insert-precise-note~ (~M-i~)
+ - Precise notes always create a new note, even if you choose an existing
+ prior heading.
+ - Precise notes are linked to a specific point on the page specified with
+ vertical and horizontal coordinates.
+ - The multicolumn property ~COLUMN_EDGES~, set by
+ ~org-noter-pdf-set-columns~, governs the ordering of precise notes on a
+ page.
+ - If no title is specified, then default title "Notes for page
V: % H:
+ %" is used, where is the pagelabel if it exists or the page number,
+ is the vertical distance from the top and is the horizontal
+ position from the left.
+ - The behavior with selected text (default title, quoting in the body) is the
+ same as for ~insert-note~.
+
+** No-questions note insertion
+ - ~defcustom org-noter-insert-note-no-questions~ is default ~nil~. If set to
+ ~t~, the note title minibuffer prompt is bypassed and a note is always
+ create with the default title. Activate this setting if you rarely or
+ never type in your own titles.
+ - Both note insertion styles have a ~toggle-no-questions~ variant to get the
+ non-default behavior.
+ - Default keybinding for the ~toggle-no-questions~ variant adds the
+ control-key (~C-i~ and ~C-M-i~, respectively).
+** Highlighting
+ - ~defcustom org-noter-highlight-selected-text~ controls the default
+ highlighting behavior of selected text.
+ - ~C-u~ prefix to any note insertion command toggles this behavior
diff --git a/docs/org_noter_tech_notes.org b/docs/org_noter_tech_notes.org
new file mode 100644
index 0000000..3bd5aef
--- /dev/null
+++ b/docs/org_noter_tech_notes.org
@@ -0,0 +1,172 @@
+:PROPERTIES:
+:ID: 4333050B-D293-4A41-8A14-00E6248FD17B
+:DRILL_LAST_INTERVAL: -1.0
+:DRILL_REPEATS_SINCE_FAIL: 1
+:DRILL_TOTAL_REPEATS: 1
+:DRILL_FAILURE_COUNT: 1
+:DRILL_AVERAGE_QUALITY: 1.0
+:DRILL_EASE: 2.5
+:NEXT_REVIEW: [2022-12-29 Thu]
+:MATURITY: seedling
+:LAST_REVIEW: [2022-12-30 Fri]
+:END:
+#+title: org-noter-tech-notes
+#+filetags: :seedling:
+
+Context for developing org-noter.
+
+* TOC :TOC:
+- [[#brief-history-of-org-noter][Brief history of org-noter]]
+- [[#tech-notes][Tech Notes]]
+ - [[#session][Session]]
+ - [[#hooks][Hooks]]
+ - [[#notes][Notes]]
+ - [[#locations][Locations]]
+ - [[#note-taking-behavior][Note taking behavior]]
+ - [[#notes-file][Notes file]]
+- [[#solove-nothing-to-hide][solove-nothing-to-hide]]
+ - [[#note-from-page-1][Note from page 1]]
+- [[#development][Development]]
+ - [[#unit-tests][Unit tests]]
+
+* Brief history of org-noter
+
+[[https://github.com/weirdNox/org-noter][org-noter]] (2018-2020), a re-implementation of the [[https://github.com/rudolfochrist/interleave/][interleave packaage]] (2015-2018) by weirdNox:
+
+#+begin_quote
+Yeah, I made org-noter because it is something I need for studying everyday, but I bet that if I didn't use it, then I would probably lose interest too... We don't have time for everything, so decisions must be made :P
+#+end_quote
+
+ [[https://github.com/rudolfochrist/interleave/issues/55][source]]
+
+In early 2022, c1-g created a fork, [[https://github.com/c1-g/org-noter-plus-djvu][org-noter-plus-djvu]] that split up note creation functionality from the underlying document format making it possible to take notes with pdf, epub and djvu documents.
+
+
+* Tech Notes
+
+** Session
+org-noter session contains all the relevant info for the current session.
+- notes file (or buffer)
+- backing document
+- ...
+
+prereq for getting everything else going see =make-org-noter-session=.
+
+** Hooks
+Hooks are used extensively to manage "non-core" functionality, that is functionality that is mode dependent.
+
+There are mode specific implementation in =modules/= directory, as well as in =tests/= (for testing).
+
+
+*** Errors
+An error like so:
+#+begin_src shell
+ Lisp nesting exceeds ‘max-lisp-eval-depth’
+#+end_src
+
+possibly indicates that a ~run-hook-with-args-until-success~ has failed:
+
+#+begin_src elisp
+ (run-hook-with-args-until-success 'org-noter-set-up-document-hook document-property-value)
+#+end_src
+
+in my experience the error indicates a problem with one of the hooks. For example in the code above one of the hooks in =org-noter-set-up-document-hook= may not be valid elisp code (requires more than one argument or another lisp issue).
+
+I haven't figured out a good way to identify these.
+
+** Notes
+
+There are two types of notes:
+
+- regular notes
+- precise notes
+
+
+
+*** Regular Notes
+
+Notes attached to a "page".
+
+#+begin_src org-mode
+:PROPERTIES:
+:NOTER_PAGE: 2
+:END:
+#+end_src
+
+
+*** Precise notes
+
+Precise notes include a coordinate vector that allows to identify the selection in the backing document and act accordingly, ie create a highlight.
+
+#+begin_src org-mode
+:PROPERTIES:
+:NOTER_PAGE: (75 0.14417344173441735 0.7955390334572491 0.6834688346883468 0.8199091284593144)
+:END:
+#+end_src
+
+** Locations
+
+Location is a property of every note (see [[Notes]]).
+
+The concept seems to be poorly defined currently, and most of the code lives in =core=, but maybe it should move to document implementation.
+
+Currently it's either
+
+=:NOTER_PAGE: (75 0.14417344173441735 0.7955390334572491 0.6834688346883468 0.8199091284593144)=
+
+or
+
+=:NOTER_PAGE: 2=
+
+** Note taking behavior
+
+Sophisticated note taking behavior is possible, based on selection size, etc see [[https://github.com/petermao/org-noter/blob/doc/README.org][Peter's matrix]].
+
+
+** Notes file
+
+=notes.org= is in this format:
+
+#+begin_src org-mode
+:PROPERTIES:
+:ID: FAKE_90283
+:END:
+#+TITLE: Test book notes
+
+* solove-nothing-to-hide
+:PROPERTIES:
+:NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
+:END:
+** Note from page 1
+:PROPERTIES:
+:NOTER_PAGE: 99
+:END:
+#+end_src
+
+Omitting the header causes =org-noter--parse-root= to work incorrectly.
+
+* Development
+** Unit tests
+*** Requirements
+
+- [[https://github.com/cask/cask][Cask]], a project management tool for Emacs
+- [[https://github.com/jorgenschaefer/emacs-buttercup][Buttercup]], behavior driven Emacs testing
+
+*** Mac
+
+#+begin_src shell
+ brew install cask
+ cask # install dependencies
+ cask exec buttercup -L . # exec unit tests, in the root of the project
+#+end_src
+
+*** GNU/Linux
+
+#+begin_src shell
+ git clone https://github.com/cask/cask.git
+ make -C cask install
+
+ cd
+ cask # install dependencies (in root of project, 1 time)
+ cask exec buttercup -L . # exec unit tests, in the root of the project
+#+end_src
diff --git a/docs/pre-merge_notes.org b/docs/pre-merge_notes.org
new file mode 100644
index 0000000..3ebded9
--- /dev/null
+++ b/docs/pre-merge_notes.org
@@ -0,0 +1,210 @@
+* Pre-merge deltas w/ *dmitrym0*
+ In the diffs below the color coding is
+ #+begin_src diff
+- Dmitry [f3f5a05]
++ Peter [6488cc6]
+ #+end_src
+** DONE *-get-buffer-file-name-*
+#+begin_src diff
+-(defun org-noter-get-buffer-file-name-* (&optional major-mode)
++(defun org-noter-get-buffer-file-name-* (mode)
+ (bound-and-true-p *-file-name))
+
++(add-to-list 'org-noter-get-buffer-file-name-hook #'org-noter-get-buffer-file-name-*)
+#+end_src
+
+ - =major-mode= is a native elisp function, =mode= is a better name
+ - the arg is not used, so the =&optional= is appropriate
+ - for the =pdf= variant, we both use =(&optional major-mode)=
+
+ proposal: =(&optional mode)= or remove the argument completely.
+
+ ACTIONS: Do this in our own repos before merge
+ 1. major-mode -> mode in module files
+ 2. use &optional when the argument is not used in the function
+
+** DONE -get-buffer-file-name-hook
+#+begin_src diff
+-(defcustom org-noter-get-buffer-file-name-hook '(org-noter-get-buffer-file-name-nov org-noter-get-buffer-file-name-pdf)
++(defcustom org-noter-get-buffer-file-name-hook nil
+#+end_src
+
+ should be nil in =org-noter-core= and set in modules.
+
+ ACTION: already converged
+** DONE *-get-precise-info-*
+#+begin_src diff
+-(defun org-noter-*--get-precise-info (major-mode)
++(defun org-noter-*--get-precise-info (major-mode window)
+ (when (eq major-mode 'djvu-read-mode)
+ (if (region-active-p)
+ (cons (mark) (point))
+- (while (not (and (eq 'mouse-1 (car event))
+- (eq window (posn-window (event-start event)))))
+- (setq event (read-event "Click where you want the start of the note to be!")))
+- (posn-point (event-start event)))))
++ (let ((event nil))
++ (while (not (and (eq 'mouse-1 (car event))
++ (eq window (posn-window (event-start event)))))
++ (setq event (read-event "Click where you want the start of the note to be!")))
++ (posn-point (event-start event))))))
+#+end_src
+
+ - calling function already calls =org-noter--get-doc-window=
+ - =window= is used in all document modes
+
+ proposal: change =major-mode= to =mode=, pass in =window=
+
+ ACTION: (done) Dmitry took mine
+** DONE *-goto-location
+#+begin_src diff
+-(defun org-noter-pdf-goto-location (mode location)
++(defun org-noter-pdf-goto-location (mode location window)
+ (when (memq mode '(doc-view-mode pdf-view-mode))
+ (let ((top (org-noter--get-location-top location))
+- (window (org-noter--get-doc-window))
+ (left (org-noter--get-location-left location)))
+#+end_src
+ - calling function already calls =org-noter--get-doc-window=
+ - nov and djvu don't need the =window= argument
+
+ proposal: we discuss this one, but I think it's better to not call functions
+ unnecessarily
+
+ ACTION:
+ pass in window, use &optional as appropriate.
+
+** DONE *-check-location-property
+ #+begin_src diff
+ (defun org-noter-pdf-check-location-property (&optional property)
+ "Check if PROPERTY is a valid location property"
+- (equal 5 (length (read property))))
++ t)
+ #+end_src
+
+ location can be
+ 1. page
+ 2. page v-pos
+ 3. page v-pos . h-pos
+
+ neither function works properly. need to read the calling function to
+ determine course of action..
+
+ ACTION: done, gone on Dmitry's side.
+ P: check diff, remove if it's still there.
+** DONE -doc--get-precise-info
+#+begin_src diff
++(defun org-noter-doc--get-precise-info (major-mode window)
++ (when (eq major-mode 'doc-view-mode)
+ (let ((event nil))
+ (while (not (and (eq 'mouse-1 (car event))
+ (eq window (posn-window (event-start event)))))
+ (setq event (read-event "Click where you want the start of the note to be!")))
+- (let ((col-row (posn-col-row (event-start event))))
+- (org-noter--conv-page-scroll-percentage (+ (window-vscroll) (cdr col-row))
+- (+ (window-hscroll) (car col-row))))))))
++ (org-noter--conv-page-scroll-percentage (+ (window-vscroll)
++ (cdr (posn-col-row (event-start event))))))))
+#+end_src
+Dmitry removed this function at [9d437bf]
+
+ACTION: Dmitry revive on his side.
+** DONE --doc-approx-location-hook
+ #+begin_src diff
+ (defcustom org-noter--doc-approx-location-hook nil
+- "This returns an approximate location if no precise info is passed: (PAGE 0)
+- or if precise info is passed, it's (PAGE 0 0 0 0) where 0s are the precise coords)
+-"
++ "TODO"
+ :group 'org-noter
+ :type 'hook)
+ #+end_src
+
+ docstring needs to be updated.
+
+ ACTION: Dmitry reverted
+** DONE --note-search-no-recurse :11fc0a8:9dfac53:
+#+begin_src diff
++(defconst org-noter--note-search-no-recurse (delete 'headline (append org-element-all-elements nil))
++ "List of elements that shouldn't be recursed into when searching for notes.")
+#+end_src
+
+ called in =org-noter--get-view-info= by =org-element-map=
+#+begin_src diff
+- nil nil (delete 'headline (append org-element-all-elements nil))))
++ nil nil org-noter--note-search-no-recurse)
+#+end_src
+
+ but this defconst is used by =org-noter--map-ignore-headings-with-doc-file=, which is
+ used by all of the sync functions
+
+ probably should keep it, and since we keep it, use it in
+ =org-noter--get-view-info=
+
+ ACTION: safe for Dmitry to cherry-pick these commits, but
+ =with-current-buffer= call gets removed. This is the one change I took from
+ ~cbpnk~
+** DONE org-noter--create-session :9dfac53:
+ #+begin_src diff
+ (defun org-noter--create-session (ast document-property-value notes-file-path)
+ (let* ((raw-value-not-empty (> (length (org-element-property :raw-value ast)) 0))
+- (link-p (or (string-match-p org-bracket-link-regexp document-property-value)
++ (link-p (or (string-match-p org-link-bracket-re document-property-value)
+ (string-match-p org-noter--url-regexp document-property-value)))
+ #+end_src
+ =org-bracket-link-regexp= is obsolete. keep mine.
+
+ ACTION: safe for Dmitry to cherry-pick
+** DONE org-noter--narrow-to-root (ast) :dfe7df2:
+#+begin_src diff
+- (when ast
++ (when (and ast (not (org-noter--no-heading-p)))
+ (save-excursion
+ (goto-char (org-element-property :contents-begin ast))
+ (org-show-entry)
+- (when (org-at-heading-p) (org-narrow-to-subtree))
++ (org-narrow-to-subtree)
+ (org-cycle-hide-drawers 'all))))
+#+end_src
+ "I don't really understand this bit of code, especially what `ast' is, but
+ it breaks narrowing when multiple documents' notes are stored in a single
+ file."
+
+ ACTION: safe for Dmitry to cherry-pick
+** DONE org-noter--get-location-page (location) :DM:629fbb6:
+ #+begin_src diff
+ "Get the page number given a LOCATION of form (page top . left) or (page . top)."
+- (message "===> %s" location)
+- (if (listp location)
+- (car location)
+- location))
++ (car location))
+ #+end_src
+
+ ACTION: Peter -- what happens with a page note (no precise location)? does (car location) make an
+ error?
+ Answer: No, (car location) works fine because for a page note, location is a
+ cons cell, e.g. (19 . 0) by the time it reaches this function.
+
+ @DM -- I think we should go back to the original (car location).
+
+ HISTORY:
+ - 5bc5754 Ahmed Shariff original code
+ - c1ed245 c1g moved code from org-noter.el to org-noter-core.el, changing
+ function name
+ - 629fbb6 introduced by DM
+
+** DONE org-noter-kill-session :9dfac53:
+ #+begin_src diff
+ (with-current-buffer notes-buffer
+ (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t)
+ (restore-buffer-modified-p nil))
+- (unless org-noter-use-indirect-buffer
++ (when org-noter-use-indirect-buffer
+ (kill-buffer notes-buffer))
+ #+end_src
+ kill the notes buffer **when** an indirect buffer is used, not **unless** it
+ is used
+
+ ACTION: safe for Dmitry to cherry-pick
+** DONE use cl-lib or native elisp hash tables rather than the =ht= package.
diff --git a/emacs-devel.el b/emacs-devel.el
new file mode 100644
index 0000000..2cecb8e
--- /dev/null
+++ b/emacs-devel.el
@@ -0,0 +1,7 @@
+(require 'cask "/opt/homebrew/share/emacs/site-lisp/cask/cask.el")
+(cask-initialize ".")
+
+(setq mac-option-modifier 'meta)
+
+(push (expand-file-name ".") load-path)
+(require 'org-noter)
diff --git a/modules/org-noter-djvu.el b/modules/org-noter-djvu.el
new file mode 100644
index 0000000..ed208e0
--- /dev/null
+++ b/modules/org-noter-djvu.el
@@ -0,0 +1,157 @@
+;;; org-noter-djvu.el --- Module for DJVU -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 c1-g
+
+;; Author: c1-g
+;; Keywords: multimedia
+
+;; 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 .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'org-noter-core)
+
+(eval-when-compile ; ensure that the compiled code knows about DJVU, if installed
+ (condition-case nil
+ (require 'djvu)
+ (error (message "`djvu' package not found"))))
+(condition-case nil ; run time warning
+ (require 'djvu)
+ (error (message "ATTENTION: org-noter-djvu needs the package `djvu'")))
+
+(push "djvu" org-noter--doc-extensions)
+
+(defun org-noter-djvu--pretty-print-location (location)
+ (org-noter--with-valid-session
+ (when (eq (org-noter--session-doc-mode session) 'djvu-read-mode)
+ (format "%s" (if (or (not (org-noter--get-location-top location)) (<= (org-noter--get-location-top location) 0))
+ (car location)
+ location)))))
+
+(add-to-list 'org-noter--pretty-print-location-hook #'org-noter-djvu--pretty-print-location)
+(add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-djvu--pretty-print-location)
+
+(defun org-noter-djvu--approx-location-cons (mode &optional precise-info _force-new-ref)
+ (when (eq mode 'djvu-read-mode)
+ (cons djvu-doc-page (if (or (numberp precise-info)
+ (and (consp precise-info)
+ (numberp (car precise-info))
+ (numberp (cdr precise-info))))
+ precise-info
+ (max 1 (/ (+ (window-start) (window-end nil t)) 2))))))
+
+(add-to-list 'org-noter--doc-approx-location-hook #'org-noter-djvu--approx-location-cons)
+
+(defun org-noter-djvu--get-precise-info (mode window)
+ (when (eq mode 'djvu-read-mode)
+ (if (region-active-p)
+ (cons (mark) (point))
+ (let ((event nil))
+ (while (not (and (eq 'mouse-1 (car event))
+ (eq window (posn-window (event-start event)))))
+ (setq event (read-event "Click where you want the start of the note to be!")))
+ (posn-point (event-start event))))))
+
+(add-to-list 'org-noter--get-precise-info-hook #'org-noter-djvu--get-precise-info)
+
+(defun org-noter-djvu--setup-handler (mode)
+ (when (eq mode 'djvu-read-mode)
+ (advice-add 'djvu-init-page :after 'org-noter--location-change-advice)
+ t))
+
+(add-to-list 'org-noter-set-up-document-hook #'org-noter-djvu--setup-handler)
+
+(defun org-noter-djvu--goto-location (mode location &optional window)
+ "DJVU mode function for `org-noter--doc-goto-location-hook'.
+MODE is the document mode and LOCATION is the note location.
+WINDOW is required by the hook, but not used in this function."
+ (when (eq mode 'djvu-read-mode)
+ (djvu-goto-page (car location))
+ (goto-char (org-noter--get-location-top location))))
+
+(add-to-list 'org-noter--doc-goto-location-hook #'org-noter-djvu--goto-location)
+
+(defun org-noter-djvu--get-current-view (mode)
+ (when (eq mode 'djvu-read-mode)
+ (vector 'paged (car (org-noter-djvu--approx-location-cons mode)))))
+
+(add-to-list 'org-noter--get-current-view-hook #'org-noter-djvu--get-current-view)
+
+(defun org-noter-djvu--get-selected-text (mode)
+ (when (and (eq mode 'djvu-read-mode)
+ (region-active-p))
+ (buffer-substring-no-properties (mark) (point))))
+
+(add-to-list 'org-noter-get-selected-text-hook #'org-noter-djvu--get-selected-text)
+
+(defun org-noter-djvu--create-skeleton (mode)
+ (when (eq mode 'djvu-read-mode)
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root))
+ (top-level (or (org-element-property :level ast) 0))
+ output-data)
+ (require 'thingatpt)
+ (with-current-buffer (djvu-ref outline-buf)
+ (unless (string= (buffer-string) "")
+ (push (vector "Skeleton" nil 1) output-data)
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (looking-at "^$"))
+ (push (vector (string-trim-right (string-trim (thing-at-point 'line t)) " [[:digit:]]+")
+ (list (string-trim-left (string-trim (thing-at-point 'line t)) ".* "))
+ (+ 2 (how-many " " (point-at-bol) (point-at-eol)))) output-data)
+ (forward-line)))))
+
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ ;; NOTE(nox): org-with-wide-buffer can't be used because we want to reset the
+ ;; narrow region to include the new headings
+ (widen)
+ (save-excursion
+ (goto-char (org-element-property :end ast))
+
+ (let (last-absolute-level
+ title location relative-level
+ level)
+
+ (dolist (data (nreverse output-data))
+ (setq title (aref data 0)
+ location (aref data 1)
+ relative-level (aref data 2))
+
+ (setq last-absolute-level (+ top-level relative-level)
+ level last-absolute-level)
+
+ (org-noter--insert-heading level title)
+
+ (when location
+ (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
+
+ (when org-noter-doc-property-in-notes
+ (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
+ (org-entry-put nil org-noter--property-auto-save-last-location "nil"))))
+
+ (setq ast (org-noter--parse-root))
+ (org-noter--narrow-to-root ast)
+ (goto-char (org-element-property :begin ast))
+ (when (org-at-heading-p) (outline-hide-subtree))
+ (org-show-children 2)))
+ output-data))))
+
+(add-to-list 'org-noter-create-skeleton-functions #'org-noter-djvu--create-skeleton)
+
+(provide 'org-noter-djvu)
+;;; org-noter-djvu.el ends here
diff --git a/modules/org-noter-nov.el b/modules/org-noter-nov.el
new file mode 100644
index 0000000..9786ffb
--- /dev/null
+++ b/modules/org-noter-nov.el
@@ -0,0 +1,207 @@
+;;; org-noter-nov.el --- Integration with Nov.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 c1-g
+
+;; Author: c1-g
+;; Keywords: multimedia
+
+;; 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 .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'org-noter-core)
+
+(eval-when-compile ; ensure that the compiled code knows about NOV, if installed
+ (condition-case nil
+ (require 'nov)
+ (error (message "`nov' package not found"))))
+(condition-case nil ; run time warning
+ (require 'nov)
+ (error (message "ATTENTION: org-noter-nov needs the package `nov'")))
+
+(push "epub" org-noter--doc-extensions)
+
+(defvar nov-documents-index)
+(defvar nov-file-name)
+(defvar-local org-noter--nov-timer nil
+ "Timer for synchronizing notes after scrolling.")
+
+(defun org-noter-nov--get-buffer-file-name (&optional _mode)
+ (bound-and-true-p nov-file-name))
+
+(add-to-list 'org-noter-get-buffer-file-name-hook #'org-noter-nov--get-buffer-file-name)
+
+(defun org-noter-nov--approx-location-cons (mode &optional precise-info _force-new-ref)
+ (when (eq mode 'nov-mode)
+ (cons nov-documents-index (if (or (numberp precise-info)
+ (and (consp precise-info)
+ (numberp (car precise-info))
+ (numberp (cdr precise-info))))
+ precise-info
+ (max 1 (/ (+ (window-start) (window-end nil t)) 2))))))
+
+(add-to-list 'org-noter--doc-approx-location-hook #'org-noter-nov--approx-location-cons)
+
+(defun org-noter-nov--scroll-handler (&rest _)
+ (when org-noter--nov-timer (cancel-timer org-noter--nov-timer))
+ (unless org-noter--inhibit-location-change-handler
+ (setq org-noter--nov-timer (run-with-timer 0.25 nil 'org-noter--doc-location-change-handler))))
+
+(defun org-noter-nov--setup-handler (mode)
+ (when (eq mode 'nov-mode)
+ (advice-add 'nov-render-document :after 'org-noter-nov--scroll-handler)
+ (add-hook 'window-scroll-functions 'org-noter-nov--scroll-handler nil t)
+ t))
+
+(add-to-list 'org-noter-set-up-document-hook #'org-noter-nov--setup-handler)
+
+(defun org-noter-nov--no-sessions-remove-advice ()
+ "Remove nov-specific advice when all sessions are closed."
+ (advice-remove 'nov-render-document 'org-noter-nov--scroll-handler))
+
+(add-to-list 'org-noter--no-sessions-remove-advice-hooks #'org-noter-nov--no-sessions-remove-advice)
+
+(defun org-noter-nov--pretty-print-location (location)
+ (org-noter--with-valid-session
+ (when (eq (org-noter--session-doc-mode session) 'nov-mode)
+ (format "%s" (if (or (not (org-noter--get-location-top location)) (<= (org-noter--get-location-top location) 1))
+ (org-noter--get-location-page location)
+ location)))))
+
+(add-to-list 'org-noter--pretty-print-location-hook #'org-noter-nov--pretty-print-location)
+(add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-nov--pretty-print-location)
+
+(defun org-noter-nov--get-precise-info (mode window)
+ (when (eq mode 'nov-mode)
+ (if (region-active-p)
+ (cons (mark) (point))
+ (let ((event nil))
+ (while (not (and (eq 'mouse-1 (car event))
+ (eq window (posn-window (event-start event)))))
+ (setq event (read-event "Click where you want the start of the note to be!")))
+ (posn-point (event-start event))))))
+
+(add-to-list 'org-noter--get-precise-info-hook #'org-noter-nov--get-precise-info)
+
+(defun org-noter-nov--goto-location (mode location &optional _window)
+ (when (eq mode 'nov-mode)
+ (setq nov-documents-index (org-noter--get-location-page location))
+ (nov-render-document)
+ (goto-char (org-noter--get-location-top location))
+ ;; NOTE(nox): This needs to be here, because it would be issued anyway after
+ ;; everything and would run org-noter--nov-scroll-handler.
+ (recenter)))
+
+(add-to-list 'org-noter--doc-goto-location-hook #'org-noter-nov--goto-location)
+
+(defun org-noter-nov--get-current-view (mode)
+ (when (eq mode 'nov-mode)
+ (vector 'nov
+ (org-noter-nov--approx-location-cons mode (window-start))
+ (org-noter-nov--approx-location-cons mode (window-end nil t)))))
+
+(add-to-list 'org-noter--get-current-view-hook #'org-noter-nov--get-current-view)
+
+(defun org-noter-nov--get-selected-text (mode)
+ (when (and (eq mode 'nov-mode) (region-active-p))
+ (buffer-substring-no-properties (mark) (point))))
+
+(add-to-list 'org-noter-get-selected-text-hook #'org-noter-nov--get-selected-text)
+
+
+;; Shamelessly stolen code from Yuchen Li.
+;; This code is originally from org-noter-plus package.
+;; At https://github.com/yuchen-lea/org-noter-plus
+
+(defun org-noter-nov--handle-toc-item (ol depth)
+ (mapcar (lambda (li)
+ (mapcar (lambda (a-or-ol)
+ (pcase-exhaustive (dom-tag a-or-ol)
+ ('a
+ (vector :depth depth
+ :title (dom-text a-or-ol)
+ :href (esxml-node-attribute 'href a-or-ol)))
+ ('ol
+ (org-noter-nov--handle-toc-item a-or-ol
+ (1+ depth)))))
+ (dom-children li)))
+ (dom-children ol)))
+
+(defun org-noter-nov--create-skeleton-epub (mode)
+ "Epub outline with nov link."
+ (when (eq mode 'nov-mode)
+ (require 'esxml)
+ (require 'nov)
+ (require 'dom)
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root))
+ (top-level (or (org-element-property :level ast) 0))
+ output-data)
+ (with-current-buffer (org-noter--session-doc-buffer session)
+ (let* ((toc-path (cdr (aref nov-documents 0)))
+ (toc-tree (with-temp-buffer
+ (insert (nov-ncx-to-html toc-path))
+ (goto-char (point-min))
+ (while (re-search-forward "\n" nil t)
+ (replace-match "" nil nil))
+ (libxml-parse-html-region (point-min)
+ (point-max))))
+ (origin-index nov-documents-index)
+ (origin-point (point)))
+ (dolist (item
+ (nreverse (flatten-tree (org-noter-nov--handle-toc-item toc-tree 1))))
+ (let ((relative-level (aref item 1))
+ (title (aref item 3))
+ (url (aref item 5)))
+ (apply 'nov-visit-relative-file
+ (nov-url-filename-and-target url))
+ (when (not (integerp nov-documents-index))
+ (setq nov-documents-index 0))
+ (push (vector title (list nov-documents-index (point)) relative-level) output-data)))
+ (push (vector "Skeleton" (list 0) 1) output-data)
+
+ (nov-goto-document origin-index)
+ (goto-char origin-point)))
+ (save-excursion
+ (goto-char (org-element-property :end ast))
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (dolist (data output-data)
+ (let* ((title (aref data 0))
+ (location (aref data 1))
+ (relative-level (aref data 2))
+ (last-absolute-level (+ top-level relative-level))
+ (level last-absolute-level))
+
+ (org-noter--insert-heading level title)
+
+ (when location
+ (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
+
+ (when org-noter-doc-property-in-notes
+ (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
+ (org-entry-put nil org-noter--property-auto-save-last-location "nil"))))
+ (setq ast (org-noter--parse-root))
+ (org-noter--narrow-to-root ast)
+ (goto-char (org-element-property :begin ast))
+ (outline-hide-subtree)
+ (org-show-children 2)))
+ output-data))))
+
+(add-to-list 'org-noter-create-skeleton-functions #'org-noter-nov--create-skeleton-epub)
+
+(provide 'org-noter-nov)
+;;; org-noter-nov.el ends here
diff --git a/modules/org-noter-pdf.el b/modules/org-noter-pdf.el
new file mode 100644
index 0000000..fa91777
--- /dev/null
+++ b/modules/org-noter-pdf.el
@@ -0,0 +1,521 @@
+;;; org-noter-pdf.el --- Modules for PDF-Tools and DocView mode -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 c1-g
+
+;; Author: c1-g
+;; Keywords: multimedia
+
+;; 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 .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(eval-when-compile (require 'subr-x))
+(require 'cl-lib)
+(require 'org-noter-core)
+(eval-when-compile ; ensure that the compiled code knows about PDF-TOOLS, if installed
+ (condition-case nil
+ (require 'pdf-tools)
+ (error (message "`pdf-tools' package not found"))))
+(condition-case nil ; inform user at run time if pdf-tools is missing
+ (require 'pdf-tools)
+ (error (message "ATTENTION: org-noter-pdf has many featues that depend on the package `pdf-tools'")))
+
+(push "pdf" org-noter--doc-extensions)
+(cl-defstruct pdf-highlight page coords)
+
+(defun org-noter-pdf--get-highlight ()
+ "If there's an active pdf selection, returns a that contains all
+the relevant info (page, coordinates)
+
+Otherwise returns nil"
+ (if-let* ((_ (pdf-view-active-region-p))
+ (page (image-mode-window-get 'page))
+ (coords (pdf-view-active-region)))
+ (make-pdf-highlight :page page :coords coords)
+ nil))
+
+(add-to-list 'org-noter--get-highlight-location-hook 'org-noter-pdf--get-highlight)
+
+(defun org-noter-pdf--pretty-print-highlight (highlight-info)
+ (format "%s" highlight-info))
+
+(add-to-list 'org-noter--pretty-print-highlight-location-hook #'org-noter-pdf--pretty-print-highlight)
+
+(defun org-noter-pdf--approx-location-cons (mode &optional precise-info _force-new-ref)
+ "Return location as a cons cell.
+Runs when MODE is `doc-view-mode' or `pdf-view-mode'
+
+Returns page location as (page . 0). When processing
+PRECISE-INFO, return (page v-pos) or (page v-pos . h-pos)."
+ (when (memq mode '(doc-view-mode pdf-view-mode))
+ (cons (image-mode-window-get 'page) (if (or (numberp precise-info)
+ (and (consp precise-info)
+ (numberp (car precise-info))
+ (numberp (cdr precise-info))))
+ precise-info 0))))
+
+(add-to-list 'org-noter--doc-approx-location-hook #'org-noter-pdf--approx-location-cons)
+
+(defun org-noter-pdf--get-buffer-file-name (&optional _mode)
+ "Return the file naming backing the document buffer.
+
+MODE (unused) is required for this type of hook."
+ (bound-and-true-p pdf-file-name))
+
+(add-to-list 'org-noter-get-buffer-file-name-hook #'org-noter-pdf--get-buffer-file-name)
+
+(defun org-noter-pdf--pdf-view-setup-handler (mode)
+ (when (eq mode 'pdf-view-mode)
+ ;; (setq buffer-file-name document-path)
+ (pdf-view-mode)
+ (add-hook 'pdf-view-after-change-page-hook 'org-noter--doc-location-change-handler nil t)
+ t))
+
+(add-to-list 'org-noter-set-up-document-hook #'org-noter-pdf--pdf-view-setup-handler)
+
+(defun org-noter-pdf--doc-view-setup-handler (mode)
+ (when (eq mode 'doc-view-mode)
+ ;; (setq buffer-file-name document-path)
+ (doc-view-mode)
+ (advice-add 'doc-view-goto-page :after 'org-noter--location-change-advice)
+ t))
+
+(add-to-list 'org-noter-set-up-document-hook #'org-noter-pdf--doc-view-setup-handler)
+
+(defun org-noter-pdf--no-sessions-remove-advice ()
+ "Remove doc-view-specific advice when all sessions are closed."
+ (advice-remove 'doc-view-goto-page 'org-noter--location-change-advice))
+
+(add-to-list 'org-noter--no-sessions-remove-advice-hooks #'org-noter-pdf--no-sessions-remove-advice)
+
+(defun org-noter-pdf--pretty-print-location (location)
+ "Formats LOCATION with full precision for property drawers."
+ (org-noter--with-valid-session
+ (when (memq (org-noter--session-doc-mode session) '(doc-view-mode pdf-view-mode))
+ (format "%s" (if (or (not (org-noter--get-location-top location)) (<= (org-noter--get-location-top location) 0))
+ (car location)
+ location)))))
+
+(add-to-list 'org-noter--pretty-print-location-hook #'org-noter-pdf--pretty-print-location)
+
+(defun org-noter-pdf--pretty-print-location-for-title (location)
+ "Convert LOCATION to a human readable format.
+With `pdf-view-mode', the format uses pagelabel and vertical and
+horizontal percentages. With `doc-view-mode', this falls back to
+original pretty-print function."
+ (org-noter--with-valid-session
+ (let ((mode (org-noter--session-doc-mode session))
+ (vpos (org-noter--get-location-top location))
+ (hpos (org-noter--get-location-left location))
+ (vtxt "") (htxt "")
+ pagelabel)
+ (cond ((eq mode 'pdf-view-mode) ; for default title, reference pagelabel instead of page
+ (if (> hpos 0)
+ (setq htxt (format " H: %d%%" (round (* 100 hpos)))))
+ (if (or (> vpos 0) (> hpos 0))
+ (setq vtxt (format " V: %d%%" (round (* 100 vpos)))))
+ (select-window (org-noter--get-doc-window))
+ (setq pagelabel (pdf-view-current-pagelabel))
+ (select-window (org-noter--get-notes-window))
+ (format "%s%s%s" pagelabel vtxt htxt))
+ ((eq mode 'doc-view-mode) ; fall back to original pp for doc-mode
+ (org-noter-pdf--pretty-print-location location))))))
+
+(add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-pdf--pretty-print-location-for-title)
+
+(defun org-noter-pdf--pdf-view-get-precise-info (mode window)
+ (when (eq mode 'pdf-view-mode)
+ (let (v-position h-position)
+ (if (pdf-view-active-region-p)
+ (let ((edges (car (pdf-view-active-region))))
+ (setq v-position (min (nth 1 edges) (nth 3 edges))
+ h-position (min (nth 0 edges) (nth 2 edges))))
+
+ (let ((event nil))
+ (while (not (and (eq 'mouse-1 (car event))
+ (eq window (posn-window (event-start event)))))
+ (setq event (read-event "Click where you want the start of the note to be!")))
+ (let* ((col-row (posn-col-row (event-start event)))
+ (click-position (org-noter--conv-page-scroll-percentage (+ (window-vscroll) (cdr col-row))
+ (+ (window-hscroll) (car col-row)))))
+ (setq v-position (car click-position)
+ h-position (cdr click-position)))))
+ (cons v-position h-position))))
+
+(add-to-list 'org-noter--get-precise-info-hook #'org-noter-pdf--pdf-view-get-precise-info)
+
+(defun org-noter-pdf--doc-view-get-precise-info (mode window)
+ (when (eq mode 'doc-view-mode)
+ (let ((event nil))
+ (while (not (and (eq 'mouse-1 (car event))
+ (eq window (posn-window (event-start event)))))
+ (setq event (read-event "Click where you want the start of the note to be!")))
+ (org-noter--conv-page-scroll-percentage (+ (window-vscroll)
+ (cdr (posn-col-row (event-start event))))))))
+
+(add-to-list 'org-noter--get-precise-info-hook #'org-noter-pdf--doc-view-get-precise-info)
+
+(defun org-noter-pdf--goto-location (mode location window)
+ (when (memq mode '(doc-view-mode pdf-view-mode))
+ (let ((top (org-noter--get-location-top location))
+ (left (org-noter--get-location-left location)))
+
+ (if (eq mode 'doc-view-mode)
+ (doc-view-goto-page (org-noter--get-location-page location))
+ (pdf-view-goto-page (org-noter--get-location-page location))
+ ;; NOTE(nox): This timer is needed because the tooltip may introduce a delay,
+ ;; so syncing multiple pages was slow
+ (when (>= org-noter-arrow-delay 0)
+ (when org-noter--arrow-location (cancel-timer (aref org-noter--arrow-location 0)))
+ (setq org-noter--arrow-location
+ (vector (run-with-idle-timer org-noter-arrow-delay nil 'org-noter--show-arrow)
+ window
+ top
+ left))))
+ (image-scroll-up (- (org-noter--conv-page-percentage-scroll top)
+ (floor (+ (window-vscroll) org-noter-vscroll-buffer)))))))
+
+(add-to-list 'org-noter--doc-goto-location-hook #'org-noter-pdf--goto-location)
+
+(defun org-noter-pdf--get-current-view (mode)
+ (when (memq mode '(doc-view-mode pdf-view-mode))
+ (vector 'paged (car (org-noter-pdf--approx-location-cons mode)))))
+
+(add-to-list 'org-noter--get-current-view-hook #'org-noter-pdf--get-current-view)
+
+(defun org-noter-pdf--get-selected-text (mode)
+ (when (and (eq mode 'pdf-view-mode)
+ (pdf-view-active-region-p))
+ (mapconcat 'identity (pdf-view-active-region-text) ? )))
+
+(add-to-list 'org-noter-get-selected-text-hook #'org-noter-pdf--get-selected-text)
+
+;; NOTE(nox): From machc/pdf-tools-org
+(defun org-noter-pdf--edges-to-region (edges)
+ "Get 4-entry region (LEFT TOP RIGHT BOTTOM) from several EDGES."
+ (when edges
+ (let ((left0 (nth 0 (car edges)))
+ (top0 (nth 1 (car edges)))
+ (bottom0 (nth 3 (car edges)))
+ (top1 (nth 1 (car (last edges))))
+ (right1 (nth 2 (car (last edges))))
+ (bottom1 (nth 3 (car (last edges)))))
+ (list left0
+ (+ top0 (/ (- bottom0 top0) 3))
+ right1
+ (- bottom1 (/ (- bottom1 top1) 3))))))
+
+(defalias 'org-noter--pdf-tools-edges-to-region 'org-noter-pdf--edges-to-region
+ "For ORG-NOTER-PDFTOOLS backward compatiblity. The name of the
+underlying function is currently under discussion")
+
+(defun org-noter-pdf--create-skeleton (mode)
+ "Create notes skeleton with the PDF outline or annotations."
+ (when (eq mode 'pdf-view-mode)
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root))
+ (top-level (or (org-element-property :level ast) 0))
+ (options '(("Outline" . (outline))
+ ("Annotations" . (annots))
+ ("Both" . (outline annots))))
+ answer output-data)
+ (with-current-buffer (org-noter--session-doc-buffer session)
+ (setq answer (assoc (completing-read "What do you want to import? " options nil t) options))
+
+ (when (memq 'outline answer)
+ (dolist (item (pdf-info-outline))
+ (let ((type (alist-get 'type item))
+ (page (alist-get 'page item))
+ (depth (alist-get 'depth item))
+ (title (alist-get 'title item))
+ (top (alist-get 'top item)))
+ (when (and (eq type 'goto-dest) (> page 0))
+ (push (vector title (cons page top) (1+ depth) nil) output-data)))))
+
+ (when (memq 'annots answer)
+ (let ((possible-annots (list '("Highlights" . highlight)
+ '("Underlines" . underline)
+ '("Squigglies" . squiggly)
+ '("Text notes" . text)
+ '("Strikeouts" . strike-out)
+ '("Links" . link)
+ '("ALL" . all)))
+ chosen-annots insert-contents pages-with-links)
+ (while (> (length possible-annots) 1)
+ (let* ((chosen-string (completing-read "Which types of annotations do you want? "
+ possible-annots nil t))
+ (chosen-pair (assoc chosen-string possible-annots)))
+ (cond ((eq (cdr chosen-pair) 'all)
+ (dolist (annot possible-annots)
+ (when (and (cdr annot) (not (eq (cdr annot) 'all)))
+ (push (cdr annot) chosen-annots)))
+ (setq possible-annots nil))
+ ((cdr chosen-pair)
+ (push (cdr chosen-pair) chosen-annots)
+ (setq possible-annots (delq chosen-pair possible-annots))
+ (when (= 1 (length chosen-annots)) (push '("DONE") possible-annots)))
+ (t
+ (setq possible-annots nil)))))
+
+ (setq insert-contents (y-or-n-p "Should we insert the annotations contents? "))
+
+ (dolist (item (pdf-info-getannots))
+ (let* ((type (alist-get 'type item))
+ (page (alist-get 'page item))
+ (edges (or (org-noter-pdf--edges-to-region (alist-get 'markup-edges item))
+ (alist-get 'edges item)))
+ (top (nth 1 edges))
+ (item-subject (alist-get 'subject item))
+ (item-contents (alist-get 'contents item))
+ name contents)
+ (when (and (memq type chosen-annots) (> page 0))
+ (if (eq type 'link)
+ (cl-pushnew page pages-with-links)
+ (setq name (cond ((eq type 'highlight) "Highlight")
+ ((eq type 'underline) "Underline")
+ ((eq type 'squiggly) "Squiggly")
+ ((eq type 'text) "Text note")
+ ((eq type 'strike-out) "Strikeout")))
+
+ (when insert-contents
+ (setq contents (cons (pdf-info-gettext page edges)
+ (and (or (and item-subject (> (length item-subject) 0))
+ (and item-contents (> (length item-contents) 0)))
+ (concat (or item-subject "")
+ (if (and item-subject item-contents) "\n" "")
+ (or item-contents ""))))))
+
+ (push (vector (format "%s on page %d" name page) (cons page top) 'inside contents)
+ output-data)))))
+
+ (dolist (page pages-with-links)
+ (let ((links (pdf-info-pagelinks page))
+ type)
+ (dolist (link links)
+ (setq type (alist-get 'type link))
+ (unless (eq type 'goto-dest) ;; NOTE(nox): Ignore internal links
+ (let* ((edges (alist-get 'edges link))
+ (title (alist-get 'title link))
+ (top (nth 1 edges))
+ (target-page (alist-get 'page link))
+ target heading-text)
+
+ (unless (and title (> (length title) 0)) (setq title (pdf-info-gettext page edges)))
+
+ (cond
+ ((eq type 'uri)
+ (setq target (alist-get 'uri link)
+ heading-text (format "Link on page %d: [[%s][%s]]" page target title)))
+
+ ((eq type 'goto-remote)
+ (setq target (concat "file:" (alist-get 'filename link))
+ heading-text (format "Link to document on page %d: [[%s][%s]]" page target title))
+ (when target-page
+ (setq heading-text (concat heading-text (format " (target page: %d)" target-page)))))
+
+ (t (error "Unexpected link type")))
+
+ (push (vector heading-text (cons page top) 'inside nil) output-data))))))))
+
+
+ (when output-data
+ (if (memq 'annots answer)
+ (setq output-data
+ (sort output-data
+ (lambda (e1 e2)
+ (or (not (aref e1 1))
+ (and (aref e2 1)
+ (org-noter--compare-locations '< (aref e1 1) (aref e2 1)))))))
+ (setq output-data (nreverse output-data)))
+
+ (push (vector "Skeleton" nil 1 nil) output-data)))
+
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ ;; NOTE(nox): org-with-wide-buffer can't be used because we want to reset the
+ ;; narrow region to include the new headings
+ (widen)
+ (save-excursion
+ (goto-char (org-element-property :end ast))
+
+ (let (last-absolute-level
+ title location relative-level contents
+ level)
+ (dolist (data output-data)
+ (setq title (aref data 0)
+ location (aref data 1)
+ relative-level (aref data 2)
+ contents (aref data 3))
+
+ (if (symbolp relative-level)
+ (setq level (1+ last-absolute-level))
+ (setq last-absolute-level (+ top-level relative-level)
+ level last-absolute-level))
+
+ (org-noter--insert-heading level title)
+
+ (when location
+ (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
+
+ (when org-noter-doc-property-in-notes
+ (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
+ (org-entry-put nil org-noter--property-auto-save-last-location "nil"))
+
+ (when (car contents)
+ (org-noter--insert-heading (1+ level) "Contents")
+ (insert (car contents)))
+ (when (cdr contents)
+ (org-noter--insert-heading (1+ level) "Comment")
+ (insert (cdr contents)))))
+
+ (setq ast (org-noter--parse-root))
+ (org-noter--narrow-to-root ast)
+ (goto-char (org-element-property :begin ast))
+ (outline-hide-subtree)
+ (org-show-children 2)))
+ output-data))))
+
+(add-to-list 'org-noter-create-skeleton-functions #'org-noter-pdf--create-skeleton)
+
+(defun org-noter-pdf--create-missing-annotation ()
+ "Add a highlight from a selected note."
+ (let ((location (org-noter--parse-location-property (org-noter--get-containing-element)))
+ (window (org-noter--get-doc-window)))
+ (org-noter-pdf--goto-location 'pdf-view-mode location window)
+ (pdf-annot-add-highlight-markup-annotation (cdr location))))
+
+(defun org-noter-pdf--highlight-location (mode precise-location)
+ "Highlight a precise location in PDF."
+ (message "---> %s %s" mode precise-location)
+ (when (and (memq mode '(doc-view-mode pdf-view-mode))
+ (pdf-view-active-region-p))
+ (pdf-annot-add-highlight-markup-annotation (pdf-view-active-region))))
+
+(add-to-list 'org-noter--add-highlight-hook #'org-noter-pdf--highlight-location)
+
+(defun org-noter-pdf--convert-to-location-cons (location)
+ "Encode precise LOCATION as a cons cell for note insertion ordering.
+Converts (page v . h) precise locations to (page v') such that
+v' represents the fractional distance through the page along
+columns, so it takes values between 0 and the number of columns.
+Each column is specified by its right edge as a fractional
+horizontal position. Output is nil for standard notes and (page
+v') for precise notes."
+ (if-let* ((_ (and (consp location) (consp (cdr location))))
+ (column-edges-string (org-entry-get nil "COLUMN_EDGES" t))
+ (right-edge-list (car (read-from-string column-edges-string)))
+ ;;(ncol (length left-edge-list))
+ (page (car location))
+ (v-pos (cadr location))
+ (h-pos (cddr location))
+ (column-index (seq-position right-edge-list h-pos #'>=)))
+ (cons page (+ v-pos column-index))))
+
+(add-to-list 'org-noter--convert-to-location-cons-hook #'org-noter-pdf--convert-to-location-cons)
+
+(defun org-noter-pdf--show-arrow ()
+ ;; From `pdf-util-tooltip-arrow'.
+ (pdf-util-assert-pdf-window)
+ (let* (x-gtk-use-system-tooltips
+ (arrow-top (aref org-noter--arrow-location 2)) ; % of page
+ (arrow-left (aref org-noter--arrow-location 3))
+ (image-top (if (floatp arrow-top)
+ (round (* arrow-top (cdr (pdf-view-image-size)))))) ; pixel location on page (magnification-dependent)
+ (image-left (if (floatp arrow-left)
+ (floor (* arrow-left (car (pdf-view-image-size))))))
+ (dx (or image-left
+ (+ (or (car (window-margins)) 0)
+ (car (window-fringes)))))
+ (dy (or image-top 0))
+ (pos (list dx dy dx (+ dy (* 2 (frame-char-height)))))
+ (vscroll (pdf-util-required-vscroll pos))
+ (tooltip-frame-parameters
+ `((border-width . 0)
+ (internal-border-width . 0)
+ ,@tooltip-frame-parameters))
+ (tooltip-hide-delay 3))
+
+ (when vscroll
+ (image-set-window-vscroll vscroll))
+ (setq dy (max 0 (- dy
+ (cdr (pdf-view-image-offset))
+ (window-vscroll nil t)
+ (frame-char-height))))
+ (when (overlay-get (pdf-view-current-overlay) 'before-string)
+ (let* ((e (window-inside-pixel-edges))
+ (xw (pdf-util-with-edges (e) e-width))
+ (display-left-margin (/ (- xw (car (pdf-view-image-size t))) 2)))
+ (cl-incf dx display-left-margin)))
+ (setq dx (max 0 (+ dx org-noter-arrow-horizontal-offset)))
+ (pdf-util-tooltip-in-window
+ (propertize
+ " " 'display (propertize
+ "\u2192" ;; right arrow
+ 'display '(height 2)
+ 'face `(:foreground
+ ,org-noter-arrow-foreground-color
+ :background
+ ,(if (bound-and-true-p pdf-view-midnight-minor-mode)
+ (cdr pdf-view-midnight-colors)
+ org-noter-arrow-background-color))))
+ dx dy)))
+
+(add-to-list 'org-noter--show-arrow-hook #'org-noter-pdf--show-arrow)
+
+(defun org-noter-pdf-set-columns (num-columns)
+ "Interactively set the COLUMN_EDGES property for the current heading.
+NUM-COLUMNS can be given as an integer prefix or in the
+minibuffer. The user is then prompted to click on the right edge
+of each column, except for the last one. Subheadings of the
+current heading inherit the COLUMN_EDGES property."
+ (interactive "NEnter number of columns: ")
+ (select-window (org-noter--get-doc-window))
+ (let (event
+ edge-list
+ (window (car (window-list))))
+ (dotimes (ii (1- num-columns))
+ (while (not (and (eq 'mouse-1 (car event))
+ (eq window (posn-window (event-start event)))))
+ (setq event (read-event (format "Click on the right boundary of column %d" (1+ ii)))))
+ (let* ((col-row (posn-col-row (event-start event)))
+ (click-position (org-noter--conv-page-scroll-percentage (+ (window-vscroll) (cdr col-row))
+ (+ (window-hscroll) (car col-row))))
+ (h-position (cdr click-position)))
+ (setq event nil)
+ (setq edge-list (append edge-list (list h-position)))))
+ (setq edge-list (append edge-list '(1)))
+ (select-window (org-noter--get-notes-window))
+ (org-entry-put nil "COLUMN_EDGES" (format "%s" (princ edge-list)))))
+
+;;; override some deleterious keybindings in pdf-view-mode.
+(define-key org-noter-doc-mode-map (kbd "C-c C-c")
+ (defun org-noter-pdf--execute-CcCc-in-notes ()
+ "Override C-c C-c in pdf document buffer."
+ (interactive)
+ (select-window (org-noter--get-notes-window))
+ (org-ctrl-c-ctrl-c)))
+
+(define-key org-noter-doc-mode-map (kbd "C-c C-x")
+ (defun org-noter-pdf--execute-CcCx-in-notes ()
+ "Override C-c C-x in pdf document buffer."
+ (interactive)
+ (let ((this-CxCc-cmd (vector (read-event))))
+ (select-window (org-noter--get-notes-window))
+ (execute-kbd-macro
+ (vconcat (kbd "C-c C-x") this-CxCc-cmd)))))
+
+(provide 'org-noter-pdf)
+;;; org-noter-pdf.el ends here
diff --git a/org-noter-core.el b/org-noter-core.el
new file mode 100644
index 0000000..82e736b
--- /dev/null
+++ b/org-noter-core.el
@@ -0,0 +1,2604 @@
+;;; org-noter-core.el --- Core functions of Org-noter -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2019 Gonçalo Santos
+
+;; Author: Gonçalo Santos (aka. weirdNox@GitHub)
+
+;; 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 .
+
+;;; Code:
+(require 'org)
+(require 'org-element)
+(require 'cl-lib)
+
+(declare-function org-noter "org-noter")
+(declare-function doc-view-goto-page "doc-view")
+(declare-function image-display-size "image-mode")
+(declare-function image-get-display-property "image-mode")
+(declare-function image-mode-window-get "image-mode")
+(declare-function image-scroll-up "image-mode")
+(declare-function org-attach-dir "org-attach")
+(declare-function org-attach-file-list "org-attach")
+
+;; --------------------------------------------------------------------------------
+;;; User variables
+(defgroup org-noter nil
+ "A synchronized, external annotator."
+ :group 'convenience
+ :version "25.3.1")
+
+(defgroup org-noter-layout nil
+ "Org-noter layout and visibility variables."
+ :group 'org-noter
+ :version "28.2")
+
+(defgroup org-noter-navigation nil
+ "Org-noter navigation and display variables."
+ :group 'org-noter
+ :version "28.2")
+
+(defgroup org-noter-insertion nil
+ "Org-noter note-insertion variables."
+ :group 'org-noter
+ :version "28.2")
+
+(defcustom org-noter-supported-modes '(doc-view-mode pdf-view-mode nov-mode djvu-read-mode)
+ "Major modes that are supported by org-noter."
+ :group 'org-noter
+ :type '(repeat symbol))
+
+(defvar org-noter--doc-extensions nil
+ "List of extensions handled by org-noter when documents are moved.
+Used by `org-noter--update-doc-rename-in-notes'. This variable gets filled in by supported modes, so it is not a `defcustom' variable.")
+
+(defcustom org-noter-property-doc-file "NOTER_DOCUMENT"
+ "Name of the property that specifies the document."
+ :group 'org-noter
+ :type 'string)
+
+(defcustom org-noter-property-note-location "NOTER_PAGE"
+ "Name of the property that specifies the location of the current note.
+The default value is still NOTER_PAGE for backwards compatibility."
+ :group 'org-noter
+ :type 'string)
+
+(defcustom org-noter-default-heading-title "Notes for page $p$"
+ "The default title for headings created with `org-noter-insert-note'.
+$p$ is replaced with the number of the page or chapter you are in
+at the moment."
+ :group 'org-noter-insertion
+ :type 'string)
+
+(defcustom org-noter-notes-window-behavior '(start scroll)
+ "Specifies situations for which the notes window is created.
+
+When the list contains:
+- `start', the notes window will be created when starting an
+ `org-noter' session.
+- `scroll', it will be created when you go to a location with an
+ associated note.
+- `only-prev', it will be created when you go to a location
+ without notes, but that has previous notes that are shown."
+ :group 'org-noter
+ :type '(set (const :tag "Session start" start)
+ (const :tag "Scroll to location with notes" scroll)
+ (const :tag "Scroll to location with previous notes only" only-prev)))
+
+(defcustom org-noter-notes-window-location 'horizontal-split
+ "The default document/notes window layout.
+Options are: \"Horizontal\", \"Vertical\", or \"Other frame\"
+
+Note that this will only have effect on session startup if `start'
+is member of `org-noter-notes-window-behavior' (which see)."
+ :group 'org-noter-layout
+ :type '(choice (const :tag "Horizontal" horizontal-split)
+ (const :tag "Vertical" vertical-split)
+ (const :tag "Other frame" other-frame)))
+
+(define-obsolete-variable-alias 'org-noter-doc-split-percentage 'org-noter-doc-split-fraction "1.2.0")
+(defcustom org-noter-doc-split-fraction '(0.5 . 0.5)
+ "Fraction of the frame that the document window will occupy when split.
+This is a cons of the type (HORIZONTAL-FRACTION . VERTICAL-FRACTION)."
+ :group 'org-noter-layout
+ :type '(cons (number :tag "Horizontal fraction") (number :tag "Vertical fraction")))
+
+(defcustom org-noter-auto-save-last-location nil
+ "Option to save document location in notes file.
+When non-nil, save the last visited location automatically; when
+starting a new session, go to that location. When nil, sessions
+start at the beginning of the document."
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-prefer-root-as-file-level nil
+ "Option to preferentially use the file-level property drawer.
+
+When non-nil, org-noter will always try to return the file-level
+property drawer even when there are headings.
+
+With the default value nil, org-noter will always use the first
+heading as root when there is at least one heading."
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-hide-other t
+ "Hide notes that are not linked to the current document page.
+When non-nil, hide all headings not related to the command used.
+For example, when scrolling to pages with notes, collapse all the
+notes that are not annotating the current page."
+ :group 'org-noter-layout
+ :type 'boolean)
+
+(defcustom org-noter-always-create-frame t
+ "Create a new frame for each document session.
+When non-nil, org-noter will always create a new frame for the
+session. When nil, it will use the selected frame if it does not
+belong to any other session."
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-disable-narrowing nil
+ "Disable narrowing in notes/org buffer."
+ :group 'org-noter-layout
+ :type 'boolean)
+
+(defcustom org-noter-use-indirect-buffer t
+ "Use indirect buffer for notes.
+When non-nil, org-noter will create an indirect buffer of the
+calling org file as a note buffer of the session. When nil, it
+will use the real buffer."
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-swap-window nil
+ "Swap the left/right or top/bottom layout of the doc and notes.
+
+By default `org-noter' will make a session by setting the buffer
+of the selected window to the document buffer then split with the
+window of the notes buffer on the right.
+
+If this variable is non-nil, the buffers of the two windows will
+be the other way around."
+ :group 'org-noter-layout
+ :type 'boolean)
+
+
+(defcustom org-noter-suggest-from-attachments t
+ "Suggest document files from attachments (in an Org file).
+When non-nil, org-noter will suggest files from the attachments
+when creating a session, if the document is missing."
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-separate-notes-from-heading nil
+ "When non-nil, add an empty line between each note's heading and content."
+ :group 'org-noter-insertion
+ :type 'boolean)
+
+(defcustom org-noter-insert-selected-text-inside-note t
+ "Option to append selected text to existing note.
+
+When non-nil (default), it will automatically append the selected
+text into an existing note.
+
+When nil, selected text will not be appended to existing
+note (not recommended)."
+ :group 'org-noter-insertion
+ :type 'boolean)
+
+(defcustom org-noter-closest-tipping-point 0.3
+ "Defines when to show the closest previous note.
+
+Let x be (this value)*100. The following schematic represents the
+view (eg., a page of a PDF):
+
++----+
+| | -> If there are notes in here, the closest previous note is not shown
++----+--> Tipping point, at x% of the view
+| | -> When _all_ notes are in here, below the tipping point, the closest
+| | previous note will be shown.
++----+
+
+When this value is negative, disable this feature.
+
+This setting may be overridden in a document with the function
+`org-noter-set-closest-tipping-point', which see."
+ :group 'org-noter-navigation
+ :type 'number)
+
+(defcustom org-noter-default-notes-file-names '("Notes.org")
+ "List of possible names for the default notes file.
+The list is in increasing order of priority."
+ :group 'org-noter
+ :type '(repeat string))
+
+(defcustom org-noter-notes-search-path '("~/Documents")
+ "List of paths to check (non recursively) when searching for a notes file."
+ :group 'org-noter
+ :type '(repeat string))
+
+(defcustom org-noter-arrow-delay 0.2
+ "Delay (in seconds) afte a sync before showing the tooltip arrow.
+
+When set to a negative number, the arrow tooltip is disabled.
+This is needed in order to keep Emacs from hanging when doing many syncs."
+ :group 'org-noter-navigation
+ :type 'number)
+
+(defcustom org-noter-arrow-horizontal-offset -20
+ "Horizontal offset of the tooltip arrow relative to a precise location.
+
+Units are display pixels; positive values move the arrow to the
+right, while negative values move it to the left. The intent is
+to move the arrow so that it does not cover text of intereest,
+but roundoff errors cause the arrow position still to be
+dependent upon magnification at the 1-em level"
+ :group 'org-noter-navigation
+ :type 'number
+ :version "28.2")
+
+(defcustom org-noter-arrow-foreground-color "orange red"
+ "Default color of the tooltip arrow."
+ :group 'org-noter-navigation
+ :type 'string
+ :version "28.2")
+
+(defcustom org-noter-arrow-background-color "white"
+ "Default background color of the tooltip arrow."
+ :group 'org-noter-navigation
+ :type 'string
+ :version "28.2")
+
+(defcustom org-noter-vscroll-buffer 5
+ "Minimum number of document display lines to leave above precise note.
+Navigation will scroll precise notes to the top of the buffer. A
+value of 0 places the precise note at the top of the window when
+possible. A positive number leaves some context above the
+precise note location."
+ :group 'org-noter-navigation
+ :type 'number
+ :version "28.2")
+
+(defcustom org-noter-doc-property-in-notes nil
+ "If non-nil, every new note will have the document property too.
+This makes moving notes out of the root heading easier."
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-insert-note-no-questions nil
+ "Do not prompt for a note title.
+When non-nil, `org-noter-insert-note' won't ask for a title and
+will always insert a new note. The title used will be the one of
+defaults: the selected text (if it does not exceed
+`org-noter-max-short-selected-text-length') or
+`org-noter-default-heading-title'."
+ :group 'org-noter-insertion
+ :type 'boolean)
+
+(defcustom org-noter-kill-frame-at-session-end t
+ "Close the frame when exiting a session.
+If non-nil, `org-noter-kill-session' will delete the frame if
+others exist on the current display.'"
+ :group 'org-noter
+ :type 'boolean)
+
+(defcustom org-noter-insert-heading-hook nil
+ "Hook being run after inserting a new heading."
+ :group 'org-noter-insertion
+ :type 'hook)
+
+(defcustom org-noter-create-session-from-document-hook '(org-noter--create-session-from-document-file-default)
+ "Hook that is invoked when `org-noter' is invoked from a document."
+ :group 'org-noter
+ :type 'hook)
+
+(defcustom org-noter-highlight-selected-text nil
+ "Highlight selected text when creating notes.
+If non-nil, highlight selected-text when creating notes. This
+variable is temporarily toggled by prefixing the insertion
+command with any non-nil prefix such as \\[universal-argument]."
+ :group 'org-noter-insertion
+ :type 'boolean
+ :version "28.2")
+
+(defcustom org-noter-max-short-selected-text-length 80
+ "Maximum length of a short text selection.
+Short text selections are the primary default note title. When
+they are quoted in the note, they are quoted as
+``short-selected-text'' rather than inside a QUOTE-block."
+ :group 'org-noter-insertion
+ :type 'integer
+ :version "28.2")
+
+(defcustom org-noter-find-additional-notes-functions nil
+ "List of functions that map a document to an Org-noter filepath.
+
+The functions in this list must accept 1 argument, a file name.
+The argument will be given by `org-noter'.
+
+The return value must be a path to an org file. No matter if
+it's an absolute or relative path, the file name will be expanded
+to each directory set in `org-noter-notes-search-path' to test if
+it exists.
+
+If it exists, it will be listed as a candidate that `org-noter'
+will have the user select to use as the note file of the
+document."
+ :group 'org-noter
+ :type 'hook
+ :version "28.2")
+
+(defcustom org-noter-headline-title-decoration ""
+ "Decoration (emphasis) for the headline title string.
+
+If you use the Org STARTUP option 'entitiespretty', filenames
+with underscores will end up looking ugly. This string is
+prepended and appended to the document title in the top-level
+headline, making it look nicer.
+
+Reasonable choices are: /, *, =, ~, _
+
+With '/', 'The_Title' would become '/The_Title/'."
+ :group 'org-noter
+ :type 'string
+ :version "28.2")
+
+(defface org-noter-no-notes-exist-face
+ '((t
+ :foreground "chocolate"
+ :weight bold))
+ "Face for modeline note count, when 0."
+ :group 'org-noter-navigation)
+
+(defface org-noter-notes-exist-face
+ '((t
+ :foreground "SpringGreen"
+ :weight bold))
+ "Face for modeline note count, when not 0."
+ :group 'org-noter-navigation)
+
+;; --------------------------------------------------------------------------------
+;;; Integration with other packages
+(defgroup org-noter-module-hooks nil
+ "Hooks for integrating org-noter with other packages (pdfview, nov, djvu)."
+ :group 'org-noter
+ :version "28.2")
+
+(defcustom org-noter--get-location-property-hook nil
+ "The list of functions that will return the note location of an org element.
+
+These functions must accept one argument, an org element.
+These functions is used by `org-noter--parse-location-property' and
+`org-noter--check-location-property' when they can't find the note location
+of the org element given to them, that org element will be passed to
+the functions in this list."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--get-containing-element-hook '(org-noter--get-containing-heading
+ org-noter--get-containing-property-drawer)
+ "List of functions that return the Org element of a note.
+
+These functions will be called by
+`org-noter--get-containing-element' to get the Org element of the
+note at point."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter-parse-document-property-hook nil
+ "The list of functions that parse NOTER_DOCUMENT for a filename.
+Or whatever the property `org-noter-property-doc-file' is set to.
+
+This is used by `org-noter--get-or-read-document-property' and
+`org-noter--doc-file-property'.
+
+This is added for integration with other packages.
+
+For example, the module `org-noter-citar' adds the function
+`org-noter-citar-find-document-from-refs' to this list which when
+the property \"NOTER_DOCUMENT\" (the default value of
+`org-noter-property-doc-file') of an org file passed to it is a
+citation key, it will return the path to the note file associated
+with the citation key and that path will be used for other
+operations instead of the real value of the property."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter-get-buffer-file-name-hook nil
+ "Functions that when passed a major mode, return the current buffer file name.
+
+This is used by the `org-noter' command to determine the file name when
+user calls `org-noter' on a document buffer.
+
+For example, `nov-mode', a renderer for EPUB documents uses a unique variable
+called `nov-file-name' to store the file name of its document while the other
+major modes use the variable `buffer-file-name'."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter-set-up-document-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter-get-selected-text-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--check-location-property-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--parse-location-property-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--pretty-print-location-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--pretty-print-location-for-title-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--convert-to-location-cons-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--doc-goto-location-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--pretty-print-highlight-location-hook nil
+ "Hook that serializes a highlight location so that it can be stored in org."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--get-highlight-location-hook nil
+ "Hook that runs to get the location of a highlight."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--add-highlight-hook nil
+ "Hook called to highlight selected text when creating notes.
+When a note is created this will be given `MAJOR-MODE' and
+`PRECISE-INFO'. For example, this hook can be used in pdf-mode
+to add a permanent highlight to the document."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--note-after-tipping-point-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--relative-position-to-view-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--get-precise-info-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--get-current-view-hook nil
+ "TODO."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--doc-approx-location-hook nil
+ "This returns an approximate location if no precise info is passed: (PAGE 0)
+or if precise info is passed, it's (PAGE V . H)."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter-create-skeleton-functions nil
+ "List of functions that convert document outline into noter headlines.
+
+The functions will be given a major mode of the document and must
+return a non-nil value when the outline is created.
+
+Used by `org-noter-create-skeleton'."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter-open-document-functions nil
+ "Functions that gives a buffer when passed with a document property.
+Used by `org-noter--create-session' when creating a new session."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+(defcustom org-noter--show-arrow-hook nil
+ "List of functions that show precise note location in document.
+For example, see `org-noter-pdf--show-arrow'."
+ :group 'org-noter-module-hooks
+ :type 'hook)
+
+;; --------------------------------------------------------------------------------
+;;; Private variables or constants
+(cl-defstruct org-noter--session
+ id frame doc-buffer notes-buffer ast modified-tick doc-mode display-name notes-file-path property-text
+ level num-notes-in-view window-behavior window-location doc-split-fraction auto-save-last-location
+ hide-other closest-tipping-point)
+
+(defvar org-noter--sessions nil
+ "List of `org-noter' sessions.")
+
+(defvar-local org-noter--session nil
+ "Session associated with the current buffer.")
+
+(defvar org-noter--inhibit-location-change-handler nil
+ "Prevent location change from updating point in notes.")
+
+(defvar org-noter--start-location-override nil
+ "Used to open the session from the document in the right page.")
+
+(defvar org-noter--arrow-location nil
+ "A vector that shows where the arrow should appear, when idling.
+Format: [TIMER WINDOW TOP LEFT]")
+
+(defvar org-noter--completing-read-keymap (make-sparse-keymap)
+ "A `completing-read' keymap that let's the user insert spaces.")
+
+(set-keymap-parent org-noter--completing-read-keymap minibuffer-local-completion-map)
+(define-key org-noter--completing-read-keymap (kbd "SPC") 'self-insert-command)
+
+(defconst org-noter--property-behavior "NOTER_NOTES_BEHAVIOR"
+ "Property for overriding global `org-noter-notes-window-behavior'.")
+
+(defconst org-noter--property-location "NOTER_NOTES_LOCATION"
+ "Property for overriding global `org-noter-notes-window-location'.")
+
+(defconst org-noter--property-doc-split-fraction "NOTER_DOCUMENT_SPLIT_FRACTION"
+ "Property for overriding global `org-noter-doc-split-fraction'.")
+
+(defconst org-noter--property-auto-save-last-location "NOTER_AUTO_SAVE_LAST_LOCATION"
+ "Property for overriding global `org-noter-auto-save-last-location'.")
+
+(defconst org-noter--property-hide-other "NOTER_HIDE_OTHER"
+ "Property for overriding global `org-noter-hide-other'.")
+
+(defconst org-noter--property-closest-tipping-point "NOTER_CLOSEST_TIPPING_POINT"
+ "Property for overriding global `org-noter-closest-tipping-point'.")
+
+(defconst org-noter--note-search-no-recurse (delete 'headline (append org-element-all-elements nil))
+ "List of elements that shouldn't be recursed into when searching for notes.")
+
+(defconst org-noter--note-search-element-type '(headline)
+ "List of elements that should be searched for notes.")
+
+(defconst org-noter--id-text-property 'org-noter-session-id
+ "Text property used to mark the headings with open sessions.")
+
+(defvar org-noter--url-regexp
+ (concat
+ "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|"
+ "nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)"
+ "\\(//[-a-z0-9_.]+:[0-9]*\\)?"
+ (let ((chars "-a-z0-9_=#$@~%&*+\\/[:word:]")
+ (punct "!?:;.,"))
+ (concat
+ "\\(?:"
+ ;; Match paired parentheses, e.g. in Wikipedia URLs:
+ ;; http://thread.gmane.org/47B4E3B2.3050402@gmail.com
+ "[" chars punct "]+" "(" "[" chars punct "]+" ")"
+ "\\(?:" "[" chars punct "]+" "[" chars "]" "\\)?"
+ "\\|"
+ "[" chars punct "]+" "[" chars "]"
+ "\\)"))
+ "\\)")
+ "Regular expression that matches URLs.")
+
+(defvar org-noter--no-sessions-remove-advice-hooks nil
+ "List of functions to remove advice when all sessions are closed.")
+
+;; --------------------------------------------------------------------------------
+;;; Utility functions
+
+(defun org-noter--no-heading-p ()
+ "Return nil if the current buffer has atleast one heading.
+Otherwise return the maximum value for point."
+ (save-excursion
+ (and (org-before-first-heading-p) (org-next-visible-heading 1))))
+
+(defun org-noter--get-new-id ()
+ (catch 'break
+ (while t
+ (let ((id (random most-positive-fixnum)))
+ (unless (cl-loop for session in org-noter--sessions
+ when (= (org-noter--session-id session) id) return t)
+ (throw 'break id))))))
+
+(defmacro org-noter--property-or-default (name)
+ (let ((function-name (intern (concat "org-noter--" (symbol-name name) "-property")))
+ (variable (intern (concat "org-noter-" (symbol-name name)))))
+ `(let ((prop-value (,function-name ast)))
+ (cond ((eq prop-value 'disable) nil)
+ (prop-value)
+ (t ,variable)))))
+
+(defun org-noter-parse-link (s)
+ (pcase (with-temp-buffer
+ (let ((org-inhibit-startup nil))
+ (insert s)
+ (org-mode)
+ (goto-char (point-min))
+ (org-element-link-parser)))
+ (`nil nil)
+ (link link)))
+
+(defun org-noter--create-session (ast document-property-value notes-file-path)
+ (let* ((raw-value-not-empty (> (length (org-element-property :raw-value ast)) 0))
+ (link-p (or (string-match-p org-link-bracket-re document-property-value)
+ (string-match-p org-noter--url-regexp document-property-value)))
+ (display-name (if raw-value-not-empty
+ (org-element-property :raw-value ast)
+ (if link-p
+ document-property-value
+ (file-name-nondirectory document-property-value))))
+
+ (frame-name (format "Emacs Org-noter - %s" display-name))
+ (document (or (run-hook-with-args-until-success 'org-noter-open-document-functions document-property-value)
+ (if link-p
+ (progn (org-link-open-from-string document-property-value)
+ (current-buffer))
+ (find-file-noselect document-property-value))))
+ (document-major-mode (if (or link-p (eq document (current-buffer)))
+ document-property-value
+ (buffer-local-value 'major-mode document)))
+ ;; (document-buffer-name
+ ;; (generate-new-buffer-name (concat (unless raw-value-not-empty "Org-noter: ") display-name)))
+ (document-buffer document)
+
+ (notes-buffer
+ (progn (when (and org-window-config-before-follow-link link-p)
+ (set-window-configuration org-window-config-before-follow-link))
+ (if org-noter-use-indirect-buffer
+ (make-indirect-buffer
+ (or (buffer-base-buffer)
+ (current-buffer))
+ (generate-new-buffer-name (concat "Notes of " display-name)) t)
+ (current-buffer))))
+
+ (single (eq (or (buffer-base-buffer document-buffer)
+ document-buffer)
+ (or (buffer-base-buffer notes-buffer)
+ notes-buffer)))
+
+ (session
+ (make-org-noter--session
+ :id (org-noter--get-new-id)
+ :display-name display-name
+ :frame
+ (if (or org-noter-always-create-frame
+ (catch 'has-session
+ (dolist (test-session org-noter--sessions)
+ (when (eq (org-noter--session-frame test-session) (selected-frame))
+ (throw 'has-session t)))))
+ (make-frame `((name . ,frame-name) (fullscreen . maximized)))
+ (set-frame-parameter nil 'name frame-name)
+ (selected-frame))
+ :doc-mode document-major-mode
+ :property-text document-property-value
+ :notes-file-path notes-file-path
+ :doc-buffer document-buffer
+ :notes-buffer notes-buffer
+ :level (or (org-element-property :level ast) 0)
+ :window-behavior (org-noter--property-or-default notes-window-behavior)
+ :window-location (org-noter--property-or-default notes-window-location)
+ :doc-split-fraction (org-noter--property-or-default doc-split-fraction)
+ :auto-save-last-location (org-noter--property-or-default auto-save-last-location)
+ :hide-other (org-noter--property-or-default hide-other)
+ :closest-tipping-point (org-noter--property-or-default closest-tipping-point)
+ :modified-tick -1))
+
+ (target-location org-noter--start-location-override)
+ (starting-point (point)))
+
+ (add-hook 'delete-frame-functions 'org-noter--handle-delete-frame)
+ (push session org-noter--sessions)
+
+ (with-current-buffer document-buffer
+ (or (run-hook-with-args-until-success 'org-noter-set-up-document-hook document-major-mode)
+ (run-hook-with-args-until-success 'org-noter-set-up-document-hook document-property-value)
+ (error "This document handler is not supported :/"))
+
+ (org-noter-doc-mode 1)
+ (setq org-noter--session session)
+ (add-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer nil t))
+
+ (with-current-buffer notes-buffer
+ (org-noter-notes-mode 1)
+ ;; NOTE(nox): This is needed because a session created in an indirect buffer would use the point of
+ ;; the base buffer (as this buffer is indirect to the base!)
+ (goto-char starting-point)
+ (setq buffer-file-name notes-file-path
+ org-noter--session session
+ fringe-indicator-alist '((truncation . nil)))
+ (add-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer nil t)
+ (add-hook 'window-scroll-functions 'org-noter--set-notes-scroll nil t)
+ (org-noter--set-text-properties (org-noter--parse-root (vector notes-buffer document-property-value))
+ (org-noter--session-id session))
+ (unless target-location
+ (setq target-location (org-noter--parse-location-property (org-noter--get-containing-element t)))))
+
+ ;; NOTE(nox): This timer is for preventing reflowing too soon.
+ (unless single
+ (run-with-idle-timer
+ 0.05 nil
+ (lambda ()
+ ;; NOTE(ahmed-shariff): setup-window run here to avoid crash when notes buffer not setup in time
+ (org-noter--setup-windows session)
+ (with-current-buffer document-buffer
+ (let ((org-noter--inhibit-location-change-handler t))
+ (when target-location (org-noter--doc-goto-location target-location)))
+ (org-noter--doc-location-change-handler)))))))
+
+(defun org-noter--valid-session (session)
+ (when session
+ (if (and (frame-live-p (org-noter--session-frame session))
+ (buffer-live-p (org-noter--session-doc-buffer session))
+ (buffer-live-p (org-noter--session-notes-buffer session)))
+ t
+ (org-noter-kill-session session)
+ nil)))
+
+(defmacro org-noter--with-valid-session (&rest body)
+ (declare (debug (body)))
+ `(let ((session org-noter--session))
+ (when (org-noter--valid-session session)
+ (progn ,@body))))
+
+(defun org-noter--handle-kill-buffer ()
+ (org-noter--with-valid-session
+ (let ((buffer (current-buffer))
+ (notes-buffer (org-noter--session-notes-buffer session))
+ (doc-buffer (org-noter--session-doc-buffer session)))
+ ;; NOTE(nox): This needs to be checked in order to prevent session killing because of
+ ;; temporary buffers with the same local variables
+ (when (or (eq buffer notes-buffer)
+ (eq buffer doc-buffer))
+ (org-noter-kill-session session)))))
+
+(defun org-noter--handle-delete-frame (frame)
+ (dolist (session org-noter--sessions)
+ (when (eq (org-noter--session-frame session) frame)
+ (org-noter-kill-session session))))
+
+(defun org-noter--parse-root (&optional info)
+ "Parse and return the root AST.
+When used, the INFO argument may be an org-noter session or a
+vector [NotesBuffer PropertyText]. If nil, the session used will
+be `org-noter--session'."
+ (let* ((arg-is-session (org-noter--session-p info))
+ (session (or (and arg-is-session info) org-noter--session))
+ root-pos ast)
+ (cond
+ ((and (not arg-is-session) (vectorp info))
+ ;; NOTE(nox): Use arguments to find heading, by trying to find the outermost parent heading with
+ ;; the specified property
+ (let ((notes-buffer (aref info 0))
+ (wanted-prop (aref info 1)))
+ (unless (and (buffer-live-p notes-buffer) (or (stringp wanted-prop)
+ (eq 'link (org-element-type wanted-prop)))
+ (eq (buffer-local-value 'major-mode notes-buffer) 'org-mode))
+ (error "Error parsing root with invalid arguments"))
+
+ (with-current-buffer notes-buffer
+ (org-with-wide-buffer
+ (catch 'break
+ (while t
+ (let ((document-property (org-entry-get nil org-noter-property-doc-file t)))
+ (when (string= (or (run-hook-with-args-until-success 'org-noter-parse-document-property-hook document-property)
+ document-property)
+ wanted-prop)
+ (setq root-pos (copy-marker (if (and org-noter-prefer-root-as-file-level
+ (save-excursion
+ (goto-char (point-min))
+ (eq 'property-drawer (org-element-type (org-element-at-point)))))
+ (point-min)
+ (point))))))
+ (unless (org-up-heading-safe) (throw 'break t))))))))
+
+ ((org-noter--valid-session session)
+ ;; NOTE(nox): Use session to find heading
+ (or (and (= (buffer-chars-modified-tick (org-noter--session-notes-buffer session))
+ (org-noter--session-modified-tick session))
+ (setq ast (org-noter--session-ast session))) ; NOTE(nox): Cached version!
+
+ ;; NOTE(nox): Find session id text property
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (let ((pos (text-property-any (point-min) (point-max) org-noter--id-text-property
+ (org-noter--session-id session))))
+ (when pos (setq root-pos (copy-marker pos)))))))))
+
+ (unless ast
+ (unless root-pos (if (or org-noter-prefer-root-as-file-level (org-noter--no-heading-p))
+ (setq root-pos (copy-marker (point-min)))
+ (org-next-visible-heading 1)
+ (setq root-pos (copy-marker (point)))))
+ (with-current-buffer (marker-buffer root-pos)
+ (org-with-point-at (marker-position root-pos)
+ (org-back-to-heading-or-point-min t)
+ (if (org-at-heading-p)
+ (org-narrow-to-subtree)
+ (org-hide-drawer-toggle 'force))
+ (setq ast (car (org-element-contents (org-element-parse-buffer 'greater-element))))
+ (when (and (not (vectorp info)) (org-noter--valid-session session))
+ (setf (org-noter--session-ast session) ast
+ (org-noter--session-modified-tick session) (buffer-chars-modified-tick))))))
+ ast))
+
+(defun org-noter--get-properties-end (ast &optional force-trim)
+ (when ast
+ (let* ((contents (org-element-contents ast))
+ (section (org-element-map contents 'section 'identity nil t 'headline))
+ (properties (or (org-element-map section 'property-drawer 'identity nil t)
+ (org-element-map contents 'property-drawer 'identity nil t)))
+ properties-end)
+ (if (not properties)
+ (org-element-property :contents-begin ast)
+ (setq properties-end (org-element-property :end properties))
+ (when (or force-trim
+ (= (org-element-property :end section) properties-end))
+ (while (not (eq (char-before properties-end) ?:))
+ (setq properties-end (1- properties-end))))
+ properties-end))))
+
+(defun org-noter--set-text-properties (ast id)
+ (org-with-wide-buffer
+ (when ast
+ (let* ((level (or (org-element-property :level ast) 0))
+ (begin (org-element-property :begin ast))
+ (title-begin (+ 1 level begin))
+ (contents-begin (org-element-property :contents-begin ast))
+ (properties-end (org-noter--get-properties-end ast t))
+ (inhibit-read-only t)
+ (modified (buffer-modified-p)))
+ (if (= level 0)
+ (when properties-end
+ (add-text-properties contents-begin properties-end
+ `(read-only t rear-nonsticky t ,org-noter--id-text-property ,id))
+ (set-buffer-modified-p modified))
+ (add-text-properties (max 1 (1- begin)) begin '(read-only t))
+ (add-text-properties begin (1- title-begin) `(read-only t front-sticky t ,org-noter--id-text-property ,id))
+ (add-text-properties (1- title-begin) title-begin '(read-only t rear-nonsticky t))
+ ;; (add-text-properties (1- contents-begin) (1- properties-end) '(read-only t))
+ (when properties-end
+ (add-text-properties (1- properties-end) properties-end
+ '(read-only t rear-nonsticky t)))
+ (set-buffer-modified-p modified))))))
+
+(defun org-noter--unset-text-properties (ast)
+ (when ast
+ (org-with-wide-buffer
+ (let* ((begin (org-element-property :begin ast))
+ (end (org-noter--get-properties-end ast t))
+ (inhibit-read-only t)
+ (modified (buffer-modified-p)))
+ (when end
+ (remove-list-of-text-properties (max 1 (1- begin)) end
+ `(read-only front-sticky rear-nonsticky ,org-noter--id-text-property))
+
+ (set-buffer-modified-p modified))))))
+
+(defun org-noter--set-notes-scroll (window &rest ignored)
+ (when window
+ (with-selected-window window
+ (org-noter--with-valid-session
+ (let* ((level (org-noter--session-level session))
+ (goal (* (1- level) 2))
+ (current-scroll (window-hscroll)))
+ (when (and (bound-and-true-p org-indent-mode) (< current-scroll goal))
+ (scroll-right current-scroll)
+ (scroll-left goal t)))))))
+
+(defun org-noter--insert-heading (level title &optional newlines-number location)
+ "Insert a new heading at LEVEL with TITLE.
+The point will be at the start of the contents, after any
+properties, by a margin of NEWLINES-NUMBER.
+
+When LOCATION is provded, it is written into the property drawer
+of the heading under `org-noter-property-note-location' (default:
+NOTER_PAGE)."
+ (setq newlines-number (or newlines-number 1))
+ (org-insert-heading nil t)
+ (let* ((initial-level (org-element-property :level (org-element-at-point)))
+ (changer (if (> level initial-level) 'org-do-demote 'org-do-promote))
+ (number-of-times (abs (- level initial-level))))
+ (dotimes (_ number-of-times) (funcall changer))
+ (insert (org-trim (replace-regexp-in-string "\n" " " title)))
+
+ (org-end-of-subtree)
+ (unless (bolp) (insert "\n"))
+ (org-N-empty-lines-before-current (1- newlines-number))
+
+ (when location
+ (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location))
+
+ (when org-noter-doc-property-in-notes
+ (org-noter--with-valid-session
+ (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
+ (org-entry-put nil org-noter--property-auto-save-last-location "nil"))))
+
+ (run-hooks 'org-noter-insert-heading-hook)))
+
+(defun org-noter--narrow-to-root (ast)
+ (when (and ast (not (org-noter--no-heading-p)))
+ (save-excursion
+ (goto-char (org-element-property :contents-begin ast))
+ (org-show-entry)
+ (org-narrow-to-subtree)
+ (org-cycle-hide-drawers 'all))))
+
+(defun org-noter--get-doc-window ()
+ (org-noter--with-valid-session
+ (or (get-buffer-window (org-noter--session-doc-buffer session)
+ (org-noter--session-frame session))
+ (org-noter--setup-windows org-noter--session)
+ (get-buffer-window (org-noter--session-doc-buffer session)
+ (org-noter--session-frame session)))))
+
+(defun org-noter--get-notes-window (&optional type)
+ "Conjure the notes-window from the void."
+ (org-noter--with-valid-session
+ (let ((notes-buffer (org-noter--session-notes-buffer session))
+ (window-location (org-noter--session-window-location session))
+ (window-behavior (org-noter--session-window-behavior session))
+ notes-window)
+ (or (get-buffer-window notes-buffer t)
+ (when (or (eq type 'force) (memq type window-behavior))
+ (if (eq window-location 'other-frame)
+ (let ((restore-frame (selected-frame)))
+ (switch-to-buffer-other-frame notes-buffer)
+ (setq notes-window (get-buffer-window notes-buffer t))
+ (x-focus-frame restore-frame)
+ (raise-frame (window-frame notes-window)))
+
+ (with-selected-window (org-noter--get-doc-window)
+ (let ((horizontal (eq window-location 'horizontal-split)))
+ (setq
+ notes-window
+ (if (window-combined-p nil horizontal)
+ ;; NOTE(nox): Reuse already existent window
+ (let ((sibling-window (or (window-next-sibling) (window-prev-sibling))))
+ (or (window-top-child sibling-window) (window-left-child sibling-window)
+ sibling-window))
+
+ (if horizontal
+ (split-window-right (ceiling (* (car (org-noter--session-doc-split-fraction session))
+ (window-total-width))))
+ (split-window-below (ceiling (* (cdr (org-noter--session-doc-split-fraction session))
+ (window-total-height)))))))))
+
+ (set-window-buffer notes-window notes-buffer))
+ notes-window)))))
+
+(defun org-noter--relocate-notes-window (notes-buffer)
+ "Clear the notes-window and (re)locate it.
+Used by interactive note-window location functions."
+ (let (exists)
+ (dolist (window (get-buffer-window-list notes-buffer nil t))
+ (setq exists t)
+ (with-selected-frame (window-frame window)
+ (if (= (count-windows) 1)
+ (delete-frame)
+ (delete-window window))))
+ (when exists (org-noter--get-notes-window 'force))))
+
+(defun org-noter--setup-windows (session)
+ "Setup windows when starting SESSION, respecting user configuration."
+ (when (org-noter--valid-session session)
+ (with-selected-frame (org-noter--session-frame session)
+ (delete-other-windows)
+ (let* ((doc-buffer (org-noter--session-doc-buffer session))
+ (doc-window (selected-window))
+ (notes-buffer (org-noter--session-notes-buffer session))
+ (window-location (org-noter--session-window-location session))
+ notes-window)
+
+ (set-window-buffer doc-window doc-buffer)
+
+ (with-current-buffer notes-buffer
+ (unless org-noter-disable-narrowing
+ (org-noter--narrow-to-root (org-noter--parse-root session)))
+ (setq notes-window (org-noter--get-notes-window 'start))
+ (org-noter--set-notes-scroll notes-window))
+
+ (when org-noter-swap-window
+ (cl-labels ((swap-windows (window1 window2)
+ "Swap the buffers of WINDOW1 and WINDOW2."
+ (let ((buffer1 (window-buffer window1))
+ (buffer2 (window-buffer window2)))
+ (set-window-buffer window1 buffer2)
+ (set-window-buffer window2 buffer1)
+ (select-window window2))))
+ (let ((frame (window-frame notes-window)))
+ (when (and (frame-live-p frame)
+ (not (eq frame (selected-frame))))
+ (select-frame-set-input-focus (window-frame notes-window)))
+ (when (and (window-live-p notes-window)
+ (not (eq notes-window doc-window)))
+ (swap-windows notes-window doc-window))))
+
+ (if (eq window-location 'horizontal-split)
+ (enlarge-window (- (ceiling (* (- 1 (car (org-noter--session-doc-split-fraction session)))
+ (frame-width)))
+ (window-total-width)) t)
+ (enlarge-window (- (ceiling (* (- 1 (cdr (org-noter--session-doc-split-fraction session)))
+ (frame-height)))
+ (window-total-height)))))
+
+ (if org-noter-swap-window
+ ;; the variable NOTES-WINDOW here is really
+ ;; the document window since the two got swapped
+ (set-window-dedicated-p notes-window t)
+ ;; It's not swapped so set it normally
+ (set-window-dedicated-p doc-window t))))))
+
+(defmacro org-noter--with-selected-notes-window (error-str &rest body)
+ (declare (debug ([&optional stringp] body)))
+ (let ((with-error (stringp error-str)))
+ `(org-noter--with-valid-session
+ (let ((notes-window (org-noter--get-notes-window)))
+ (if notes-window
+ (with-selected-window notes-window
+ ,(if with-error
+ `(progn ,@body)
+ (if body
+ `(progn ,error-str ,@body)
+ `(progn ,error-str))))
+ ,(when with-error `(user-error "%s" ,error-str)))))))
+
+(defun org-noter--notes-window-behavior-property (ast)
+ (let ((property (org-element-property (intern (concat ":" org-noter--property-behavior)) ast))
+ value)
+ (when (and (stringp property) (> (length property) 0))
+ (setq value (car (read-from-string property)))
+ (when (listp value) value))))
+
+(defun org-noter--notes-window-location-property (ast)
+ (let ((property (org-element-property (intern (concat ":" org-noter--property-location)) ast))
+ value)
+ (when (and (stringp property) (> (length property) 0))
+ (setq value (intern property))
+ (when (memq value '(horizontal-split vertical-split other-frame)) value))))
+
+(defun org-noter--doc-split-fraction-property (ast)
+ (let ((property (org-element-property (intern (concat ":" org-noter--property-doc-split-fraction)) ast))
+ value)
+ (when (and (stringp property) (> (length property) 0))
+ (setq value (car (read-from-string property)))
+ (when (consp value) value))))
+
+(defun org-noter--auto-save-last-location-property (ast)
+ (let ((property (org-element-property (intern (concat ":" org-noter--property-auto-save-last-location)) ast)))
+ (when (and (stringp property) (> (length property) 0))
+ (if (intern property) t 'disable))))
+
+(defun org-noter--hide-other-property (ast)
+ (let ((property (org-element-property (intern (concat ":" org-noter--property-hide-other)) ast)))
+ (when (and (stringp property) (> (length property) 0))
+ (if (intern property) t 'disable))))
+
+(defun org-noter--closest-tipping-point-property (ast)
+ (let ((property (org-element-property (intern (concat ":" org-noter--property-closest-tipping-point)) ast)))
+ (when (and (stringp property) (> (length property) 0))
+ (ignore-errors (string-to-number property)))))
+
+(defun org-noter--doc-approx-location (&optional precise-info force-new-ref)
+ "Return document location as (page . v) or (page v . h).
+If PRECISE-INFO is given, return the location in the same format.
+FORCE-NEW-REF is not used by PDF, NOV, or DJVU format files."
+ (let ((window (if (org-noter--valid-session org-noter--session)
+ (org-noter--get-doc-window)
+ (selected-window))))
+ (cl-assert window)
+ (with-selected-window window
+ (or (run-hook-with-args-until-success
+ 'org-noter--doc-approx-location-hook major-mode precise-info force-new-ref)
+ (error "Unknown document type %s" major-mode)))))
+
+(defun org-noter--location-change-advice (&rest _)
+ (org-noter--with-valid-session (org-noter--doc-location-change-handler)))
+
+(defsubst org-noter--doc-file-property (headline)
+ (let ((doc-prop (or (org-element-property (intern (concat ":" org-noter-property-doc-file)) headline)
+ (org-entry-get nil org-noter-property-doc-file t))))
+ (or (run-hook-with-args-until-success 'org-noter-parse-document-property-hook doc-prop)
+ doc-prop)))
+
+(defun org-noter--check-location-property (arg)
+ (let ((property (if (stringp arg) arg
+ (or (org-element-property
+ (intern (concat ":" org-noter-property-note-location)) arg)
+ (run-hook-with-args-until-success
+ 'org-noter--get-location-property-hook arg)))))
+ (when (and (stringp property) (> (length property) 0))
+ (or (run-hook-with-args-until-success 'org-noter--check-location-property-hook property)
+ (let ((value (car (read-from-string property))))
+ (or (and (consp value) (integerp (car value)) (numberp (cdr value)))
+ (and (consp value) (integerp (car value)) (numberp (cadr value)) (numberp (cddr value)))
+ (integerp value)))))))
+
+(defun org-noter--parse-location-property (arg)
+ (let ((property (if (stringp arg) arg
+ (or (org-element-property
+ (intern (concat ":" org-noter-property-note-location)) arg)
+ (run-hook-with-args-until-success
+ 'org-noter--get-location-property-hook arg)))))
+ (when (and (stringp property) (> (length property) 0))
+ (or (run-hook-with-args-until-success 'org-noter--parse-location-property-hook property)
+ (let ((value (car (read-from-string property))))
+ (cond ((and (consp value) (integerp (car value)) (numberp (cdr value))) value)
+ ((and (consp value) (integerp (car value)) (consp (cdr value)) (numberp (cadr value)) (numberp (cddr value))) value)
+ ((integerp value) (cons value 0))))))))
+
+(defun org-noter--pretty-print-location (location)
+ "Original pretty-print for property drawer.
+LOCATION contains the page number and, optionally, the vertical
+and/or horizontal positions."
+ (org-noter--with-valid-session
+ (run-hook-with-args-until-success
+ 'org-noter--pretty-print-location-hook location)))
+
+(defun org-noter--pretty-print-location-for-title (location)
+ "Pretty-print for titles.
+Compared to the original functions/hook, this one may present
+more human-readable text. LOCATION contains the page number and,
+optionally, the vertical and/or horizontal positions."
+ (org-noter--with-valid-session
+ (run-hook-with-args-until-success
+ 'org-noter--pretty-print-location-for-title-hook location)))
+
+;; TODO: Documentation
+(defun org-noter--get-containing-element (&optional include-root)
+ "Run `org-noter--get-containing-element-hook's until success.
+
+Runs `org-noter--get-containing-heading', then
+`org-noter--get-containing-property-drawer'. This function is
+used in `org-noter-sync-current-note',
+`org-noter-sync-previous-note', and `org-noter--create-session'.
+
+When INCLUDE-ROOT is non-nil, the root heading is also eligible
+to be returned."
+ (run-hook-with-args-until-success 'org-noter--get-containing-element-hook include-root))
+
+(defun org-noter--get-containing-heading (&optional include-root)
+ "Return the smallest heading around point with a location property.
+
+Get smallest containing heading that encloses the point and has
+location property. If the point isn't inside any heading with
+location property, return the outer heading. When INCLUDE-ROOT
+is non-nil, the root heading is also eligible to be returned."
+ (org-noter--with-valid-session
+ (org-with-wide-buffer
+ (unless (org-before-first-heading-p)
+ (org-back-to-heading t)
+ (let (previous)
+ (catch 'break
+ (while t
+ (let ((prop (org-noter--check-location-property (org-entry-get nil org-noter-property-note-location)))
+ (at-root (equal (org-noter--session-id session)
+ (get-text-property (point) org-noter--id-text-property)))
+ (heading (org-element-at-point)))
+ (when (and prop (or include-root (not at-root)))
+ (throw 'break heading))
+
+ (when (or at-root (not (org-up-heading-safe)))
+ (throw 'break (if include-root heading previous)))
+
+ (setq previous heading)))))))))
+
+(defun org-noter--get-containing-property-drawer (&optional include-root)
+ "Return the property drawer of the smallest heading around point with location.
+
+Get smallest containing heading that encloses the point and has
+location property. If the point isn't inside any heading with
+location property, return the outer heading. When INCLUDE-ROOT
+is non-nil, the root heading is also eligible to be returned."
+ (org-noter--with-valid-session
+ (org-with-point-at (point-min)
+ (when (org-before-first-heading-p)
+ (let ((prop (org-entry-get nil org-noter-property-note-location))
+ (at-root (equal (org-noter--session-id session)
+ (get-text-property (point) org-noter--id-text-property))))
+ (when (and (org-noter--check-location-property prop) (or include-root (not at-root)))
+ prop))))))
+
+(defun org-noter--doc-get-page-slice ()
+ "Return (slice-top . slice-height)."
+ (let* ((slice (or (image-mode-window-get 'slice) '(0 0 1 1)))
+ (slice-left (float (nth 0 slice)))
+ (slice-top (float (nth 1 slice)))
+ (slice-width (float (nth 2 slice)))
+ (slice-height (float (nth 3 slice))))
+ (when (or (> slice-top 1)
+ (> slice-height 1))
+ (let ((height (cdr (image-size (image-mode-window-get 'image) t))))
+ (setq slice-top (/ slice-top height)
+ slice-height (/ slice-height height))))
+ (when (or (> slice-width 1)
+ (> slice-left 1))
+ (let ((width (car (image-size (image-mode-window-get 'image) t))))
+ (setq slice-width (/ slice-width width)
+ slice-left (/ slice-left width))))
+ (list slice-top slice-height slice-left slice-width)))
+
+(defun org-noter--conv-page-scroll-percentage (vscroll &optional hscroll)
+ "Convert VSCROLL, HSCROLL position to percent-base position.
+Scroll units are character-based."
+ (let* ((slice (org-noter--doc-get-page-slice))
+ (display-size (image-display-size (image-get-display-property))) ;(width height)
+ (display-width (car display-size))
+ (display-height (cdr display-size))
+ (window-geom (window-inside-edges)) ; (L T R B)
+ (display-left-edge (/ (- (nth 2 window-geom) (nth 0 window-geom) display-width) 2))
+ (display-percentage-v (/ vscroll display-height))
+ (percentage-v (max 0 (min 1 (+ (nth 0 slice) (* (nth 1 slice) display-percentage-v)))))
+ (display-percentage-h 0)
+ (percentage-h 0))
+ (when hscroll
+ (setq display-percentage-h (/ (- hscroll display-left-edge) display-width)
+ percentage-h (max 0 (min 1 (+ (nth 2 slice) (* (nth 3 slice) display-percentage-h))))))
+ (cons percentage-v percentage-h)))
+
+(defun org-noter--conv-page-percentage-scroll (percentage)
+ "Convert PERCENTAGE based position to scroll-based position."
+ (let* ((slice (org-noter--doc-get-page-slice))
+ (display-height (cdr (image-display-size (image-get-display-property))))
+ (display-percentage (min 1 (max 0 (/ (- percentage (nth 0 slice)) (nth 1 slice)))))
+ (scroll (max 0 (floor (* display-percentage display-height)))))
+ scroll))
+
+(defun org-noter--get-precise-info ()
+ (org-noter--with-valid-session
+ (let ((window (org-noter--get-doc-window))
+ (mode (org-noter--session-doc-mode session)))
+ (with-selected-window window
+ (run-hook-with-args-until-success 'org-noter--get-precise-info-hook mode window)))))
+
+(defun org-noter--show-arrow ()
+ (when (and org-noter--arrow-location
+ (window-live-p (aref org-noter--arrow-location 1)))
+ (with-selected-window (aref org-noter--arrow-location 1)
+ (run-hook-with-args-until-success 'org-noter--show-arrow-hook)
+ (setq org-noter--arrow-location nil))))
+
+(defun org-noter--get-location-top (location)
+ "Get the top coordinate given a LOCATION.
+... when LOCATION has form (page top . left) or (page . top)."
+ (if (listp (cdr location))
+ (cadr location)
+ (cdr location)))
+
+(defun org-noter--get-location-page (location)
+ "Get the page number given a LOCATION of form (page top . left) or (page . top)."
+ (if (listp location)
+ (car location)
+ location))
+
+(defun org-noter--get-location-left (location)
+ "Get the left coordinate given a LOCATION.
+... when LOCATION has form (page top . left) or (page . top). If
+later form of vector is passed return 0."
+ (if (listp (cdr location))
+ (if (listp (cddr location))
+ (caddr location)
+ (cddr location))
+ 0))
+
+(defun org-noter--doc-goto-location (location)
+ "Go to location specified by LOCATION."
+ (org-noter--with-valid-session
+ (let ((window (org-noter--get-doc-window))
+ (mode (org-noter--session-doc-mode session)))
+ (with-selected-window window
+ (run-hook-with-args-until-success 'org-noter--doc-goto-location-hook mode location window)
+ (redisplay)))))
+
+(defun org-noter--compare-location-cons (comp l1 l2)
+ "Compare L1 and L2, which are location cons.
+COMP can be any of the usual comparison operators plus \">f\".
+See `org-noter--compare-locations'."
+ (cl-assert (and (consp l1) (consp l2)))
+ (cond ((eq comp '=)
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
+ (= (org-noter--get-location-left l1) (org-noter--get-location-left l2))))
+ ((eq comp '<)
+ (or (< (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (< (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
+ (< (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
+ ((eq comp '<=)
+ (or (< (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (<= (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
+ (<= (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
+ ((eq comp '>)
+ (or (> (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (> (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
+ (> (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
+ ((eq comp '>=)
+ (or (> (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (>= (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
+ (>= (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
+ ((eq comp '>f)
+ (or (> (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (< (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
+ (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
+ (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
+ (< (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
+ (t (error "Comparison operator %s not known" comp))))
+
+(defun org-noter--compare-locations (comp l1 l2)
+ "Compare L1 and L2.
+When COMP is '<, '<=, '>, or '>=, it works as expected.
+When COMP is '>f, it will return t when L1 is a page greater than
+L2 or, when in the same page, if L1 is the _f_irst of the two."
+ (cond ((not l1) nil)
+ ((not l2) t)
+ (t
+ (setq l1 (or (run-hook-with-args-until-success 'org-noter--convert-to-location-cons-hook l1) l1)
+ l2 (or (run-hook-with-args-until-success 'org-noter--convert-to-location-cons-hook l2) l2))
+ (if (numberp (cdr l2))
+ (org-noter--compare-location-cons comp l1 l2)
+ (org-noter--compare-location-cons comp l1 (cons (car l2) (cadr l2)))))))
+
+(defun org-noter--show-note-entry (session note)
+ "Show the NOTE entry and its children for this SESSION.
+Every direct subheading _until_ the first heading that doesn't
+belong to the same view (ie. until a heading with location or
+document property) will be opened."
+ (save-excursion
+ (goto-char (org-element-property :contents-begin note))
+ (org-show-set-visibility t)
+ (org-element-map (org-element-contents note) 'headline
+ (lambda (headline)
+ (let ((doc-file (org-noter--doc-file-property headline)))
+ (if (or (and doc-file (not (string= doc-file (org-noter--session-property-text session))))
+ (org-noter--check-location-property headline))
+ t
+ (goto-char (org-element-property :begin headline))
+ (org-show-entry)
+ (org-show-children)
+ nil)))
+ nil t org-element-all-elements)))
+
+(defun org-noter--focus-notes-region (view-info)
+ (org-noter--with-selected-notes-window
+ (if (org-noter--session-hide-other session)
+ (save-excursion
+ (goto-char (org-element-property :begin (org-noter--parse-root)))
+ (unless (org-before-first-heading-p)
+ (outline-hide-subtree)))
+ (org-cycle-hide-drawers 'all))
+
+ (let* ((notes-cons (org-noter--view-info-notes view-info))
+ (regions (or (org-noter--view-info-regions view-info)
+ (org-noter--view-info-prev-regions view-info)))
+ (point-before (point))
+ target-region
+ point-inside-target-region)
+ (cond
+ (notes-cons
+ (dolist (note-cons notes-cons) (org-noter--show-note-entry session (car note-cons)))
+
+ (setq target-region (or (catch 'result (dolist (region regions)
+ (when (and (>= point-before (car region))
+ (or (save-restriction (goto-char (cdr region)) (eobp))
+ (< point-before (cdr region))))
+ (setq point-inside-target-region t)
+ (throw 'result region))))
+ (car regions)))
+
+ (let ((begin (car target-region)) (end (cdr target-region)) num-lines
+ (target-char (if point-inside-target-region
+ point-before
+ (org-noter--get-properties-end (caar notes-cons))))
+ (window-start (window-start)) (window-end (window-end nil t)))
+ (setq num-lines (count-screen-lines begin end))
+
+ (cond
+ ((> num-lines (window-height))
+ (goto-char begin)
+ (recenter 0))
+
+ ((< begin window-start)
+ (goto-char begin)
+ (recenter 0))
+
+ ((> end window-end)
+ (goto-char end)
+ (recenter -2)))
+
+ (goto-char target-char)))
+
+ (t (org-noter--show-note-entry session (org-noter--parse-root)))))
+
+ (org-cycle-show-empty-lines t)))
+
+(defun org-noter--get-current-view ()
+ "Return a vector with the current view information."
+ (org-noter--with-valid-session
+ (let ((mode (org-noter--session-doc-mode session)))
+ (with-selected-window (org-noter--get-doc-window)
+ (or (run-hook-with-args-until-success 'org-noter--get-current-view-hook mode)
+ (error "Unknown document type"))))))
+
+(defun org-noter--note-after-tipping-point (point location view)
+ ;; NOTE(nox): This __assumes__ the note is inside the view!
+ (let (hook-result)
+ (cond
+ ((setq hook-result (run-hook-with-args-until-success 'org-noter--note-after-tipping-point-hook
+ point location view))
+ (cdr hook-result))
+ ((eq (aref view 0) 'paged)
+ (> (org-noter--get-location-top location) point))
+ ((eq (aref view 0) 'nov)
+ (> (org-noter--get-location-top location) (+ (* point (- (cdr (aref view 2)) (cdr (aref view 1))))
+ (cdr (aref view 1))))))))
+
+(defun org-noter--relative-position-to-view (location view)
+ (cond
+ ((run-hook-with-args-until-success 'org-noter--relative-position-to-view-hook location view))
+
+ ((eq (aref view 0) 'paged)
+ (let ((note-page (org-noter--get-location-page location))
+ (view-page (aref view 1)))
+ (cond ((< note-page view-page) 'before)
+ ((= note-page view-page) 'inside)
+ (t 'after))))
+
+ ((eq (aref view 0) 'nov)
+ (let ((view-top (aref view 1))
+ (view-bot (aref view 2)))
+ (cond ((org-noter--compare-locations '< location view-top) 'before)
+ ((org-noter--compare-locations '<= location view-bot) 'inside)
+ (t 'after))))))
+
+(defmacro org-noter--view-region-finish (info &optional terminating-headline)
+ `(when ,info
+ ,(if terminating-headline
+ `(push (cons (aref ,info 1) (min (aref ,info 2) (org-element-property :begin ,terminating-headline)))
+ (gv-deref (aref ,info 0)))
+ `(push (cons (aref ,info 1) (aref ,info 2)) (gv-deref (aref ,info 0))))
+ (setq ,info nil)))
+
+(defmacro org-noter--view-region-add (info list-name headline)
+ `(progn
+ (when (and ,info (not (eq (aref ,info 3) ',list-name)))
+ (org-noter--view-region-finish ,info ,headline))
+
+ (if ,info
+ (setf (aref ,info 2) (max (aref ,info 2) (org-element-property :end ,headline)))
+ (setq ,info (vector (gv-ref ,list-name)
+ (org-element-property :begin ,headline) (org-element-property :end ,headline)
+ ',list-name)))))
+
+;; NOTE(nox): notes is a list of (HEADING . HEADING-TO-INSERT-TEXT-BEFORE):
+;; - HEADING is the root heading of the note
+;; - SHOULD-ADD-SPACE indicates if there should be extra spacing when inserting text to the note (ie. the
+;; note has contents)
+(cl-defstruct org-noter--view-info notes regions prev-regions reference-for-insertion)
+
+(defun org-noter--get-view-info (view &optional new-location)
+ "Return VIEW related information.
+
+When optional NEW-LOCATION is provided, it will be used to find
+the best heading to serve as a reference to create the new one
+relative to."
+ (when view
+ (org-noter--with-valid-session
+ (let ((contents (if (= 0 (org-noter--session-level session))
+ (org-element-contents
+ (org-element-property :parent (org-noter--parse-root)))
+ (org-element-contents (org-noter--parse-root))))
+ (preamble t)
+ notes-in-view regions-in-view
+ reference-for-insertion reference-location
+ (all-after-tipping-point t)
+ (closest-tipping-point (and (>= (org-noter--session-closest-tipping-point session) 0)
+ (org-noter--session-closest-tipping-point session)))
+ closest-notes closest-notes-regions closest-notes-location
+ ignore-until-level
+ current-region-info) ;; NOTE(nox): [REGIONS-LIST-PTR START MAX-END REGIONS-LIST-NAME]
+
+ (org-element-map contents org-noter--note-search-element-type
+ (lambda (element)
+ (let ((doc-file (org-noter--doc-file-property element))
+ (location (org-noter--parse-location-property element)))
+ (when (and ignore-until-level (<= (org-element-property :level element) ignore-until-level))
+ (setq ignore-until-level nil))
+
+ (cond
+ (ignore-until-level) ;; NOTE(nox): This heading is ignored, do nothing
+
+ ((and doc-file (not (string= doc-file (org-noter--session-property-text session))))
+ (org-noter--view-region-finish current-region-info element)
+ (setq ignore-until-level (org-element-property :level element))
+ (when (and preamble new-location
+ (or (not reference-for-insertion)
+ (>= (org-element-property :begin element)
+ (org-element-property :end (cdr reference-for-insertion)))))
+ (setq reference-for-insertion (cons 'after element))))
+
+ (location
+ (let ((relative-position (org-noter--relative-position-to-view location view)))
+ (cond
+ ((eq relative-position 'inside)
+ (push (cons element nil) notes-in-view)
+
+ (org-noter--view-region-add current-region-info regions-in-view element)
+
+ (setq all-after-tipping-point
+ (and all-after-tipping-point (org-noter--note-after-tipping-point
+ closest-tipping-point location view))))
+
+ (t
+ (when current-region-info
+ (let ((note-cons-to-change (cond ((eq (aref current-region-info 3) 'regions-in-view)
+ (car notes-in-view))
+ ((eq (aref current-region-info 3) 'closest-notes-regions)
+ (car closest-notes)))))
+ (when (< (org-element-property :begin element)
+ (org-element-property :end (car note-cons-to-change)))
+ (setcdr note-cons-to-change element))))
+
+ (let ((eligible-for-before (and closest-tipping-point all-after-tipping-point
+ (eq relative-position 'before))))
+ (cond ((and eligible-for-before
+ (org-noter--compare-locations '> location closest-notes-location))
+ (setq closest-notes (list (cons element nil))
+ closest-notes-location location
+ current-region-info nil
+ closest-notes-regions nil)
+ (org-noter--view-region-add current-region-info closest-notes-regions element))
+
+ ((and eligible-for-before (equal location closest-notes-location))
+ (push (cons element nil) closest-notes)
+ (org-noter--view-region-add current-region-info closest-notes-regions element))
+
+ (t (org-noter--view-region-finish current-region-info element)))))))
+
+ (when new-location
+ (setq preamble nil)
+ (cond ((and (org-noter--compare-locations '<= location new-location)
+ (or (eq (car reference-for-insertion) 'before)
+ (org-noter--compare-locations '>= location reference-location)))
+ (setq reference-for-insertion (cons 'after element)
+ reference-location location))
+
+ ((and (eq (car reference-for-insertion) 'after)
+ (< (org-element-property :begin element)
+ (org-element-property :end (cdr reference-for-insertion)))
+ (org-noter--compare-locations '>= location new-location))
+ (setq reference-for-insertion (cons 'before element)
+ reference-location location)))))
+
+ (t
+ (when (and preamble new-location
+ (or (not reference-for-insertion)
+ (>= (org-element-property :begin element)
+ (org-element-property :end (cdr reference-for-insertion)))))
+ (setq reference-for-insertion (cons 'after element)))))))
+ nil nil org-noter--note-search-no-recurse)
+
+ (org-noter--view-region-finish current-region-info)
+
+ (setf (org-noter--session-num-notes-in-view session) (length notes-in-view))
+
+ (when all-after-tipping-point (setq notes-in-view (append closest-notes notes-in-view)))
+
+ (make-org-noter--view-info
+ :notes (nreverse notes-in-view)
+ :regions (nreverse regions-in-view)
+ :prev-regions (nreverse closest-notes-regions)
+ :reference-for-insertion reference-for-insertion)))))
+
+(defun org-noter--make-view-info-for-single-note (session headline)
+ (let ((not-belonging-element
+ (org-element-map (org-element-contents headline) 'headline
+ (lambda (headline)
+ (let ((doc-file (org-noter--doc-file-property headline)))
+ (and (or (and doc-file (not (string= doc-file (org-noter--session-property-text session))))
+ (org-noter--check-location-property headline))
+ headline)))
+ nil t)))
+
+ (make-org-noter--view-info
+ ;; NOTE(nox): The cdr is only used when inserting, doesn't matter here
+ :notes (list (cons headline nil))
+ :regions (list (cons (org-element-property :begin headline)
+ (or (and not-belonging-element (org-element-property :begin not-belonging-element))
+ (org-element-property :end headline)))))))
+
+(defun org-noter--doc-location-change-handler ()
+ (org-noter--with-valid-session
+ (let ((view-info (org-noter--get-view-info (org-noter--get-current-view))))
+ (force-mode-line-update t)
+ (unless org-noter--inhibit-location-change-handler
+ (org-noter--get-notes-window (cond ((org-noter--view-info-regions view-info) 'scroll)
+ ((org-noter--view-info-prev-regions view-info) 'only-prev)))
+ (org-noter--focus-notes-region view-info)))
+
+ (when (org-noter--session-auto-save-last-location session) (org-noter-set-start-location))))
+
+(defun org-noter--mode-line-text ()
+ (org-noter--with-valid-session
+ (let* ((number-of-notes (or (org-noter--session-num-notes-in-view session) 0)))
+ (cond ((= number-of-notes 0) (propertize " 0 notes " 'face 'org-noter-no-notes-exist-face))
+ ((= number-of-notes 1) (propertize " 1 note " 'face 'org-noter-notes-exist-face))
+ (t (propertize (format " %d notes " number-of-notes) 'face 'org-noter-notes-exist-face))))))
+
+(defun org-noter--check-if-document-is-annotated-on-file (document-path notes-path)
+ "Check if NOTES-PATH contains any notes that annotate DOCUMENT-PATH.
+NOTES-PATH is a path to a notes files.
+DOCUMENT-PATH is a path to a document file."
+ ;; NOTE(nox): In order to insert the correct file contents
+ (let ((buffer (find-buffer-visiting notes-path)))
+ (when buffer (with-current-buffer buffer (save-buffer)))
+
+ (with-temp-buffer
+ (insert-file-contents notes-path)
+ (catch 'break
+ (while (re-search-forward (org-re-property org-noter-property-doc-file) nil t)
+ (when (string-equal (expand-file-name (match-string 3) (file-name-directory notes-path))
+ document-path)
+ ;; NOTE(nox): This notes file has the document we want!
+ (throw 'break t)))))))
+
+(defsubst org-noter--check-doc-prop (doc-prop)
+ (and doc-prop (or (string-match-p org-link-bracket-re doc-prop)
+ (string-match-p org-noter--url-regexp doc-prop)
+ (and (not (file-directory-p doc-prop)) (file-readable-p doc-prop)))))
+
+(defun org-noter--get-or-read-document-property (inherit-prop &optional force-new)
+ (let ((doc-prop (and (not force-new) (org-entry-get nil org-noter-property-doc-file inherit-prop))))
+
+ (setq doc-prop (or (run-hook-with-args-until-success 'org-noter-parse-document-property-hook doc-prop)
+ doc-prop))
+
+ (unless (org-noter--check-doc-prop doc-prop)
+ (setq doc-prop nil)
+
+ (when org-noter-suggest-from-attachments
+ (require 'org-attach)
+ (let* ((attach-dir (org-attach-dir))
+ (attach-list (and attach-dir (org-attach-file-list attach-dir))))
+ (when (and attach-list (y-or-n-p "Do you want to annotate an attached file?"))
+ (setq doc-prop (completing-read "File to annotate: " attach-list nil t))
+ (when doc-prop (setq doc-prop (file-relative-name (expand-file-name doc-prop attach-dir)))))))
+
+ (unless (org-noter--check-doc-prop doc-prop)
+ (setq doc-prop (expand-file-name
+ (read-file-name
+ (cond
+ ((null doc-prop) "No document property found. Please specify a document path: ")
+ ((file-directory-p doc-prop)
+ (format "Document property (\"%s\") is a directory. Please specify a document file: "
+ doc-prop))
+ ((not (file-readable-p doc-prop))
+ (format "The file specified by the document property \"%s\" is unreadable. Please specify a new document: "
+ doc-prop)))
+ nil nil t)))
+ (when (or (file-directory-p doc-prop) (not (file-readable-p doc-prop)))
+ (user-error "Invalid file path"))
+ (when (y-or-n-p "Do you want a relative file name? ")
+ (setq doc-prop (file-relative-name doc-prop))))
+
+ (org-entry-put nil org-noter-property-doc-file doc-prop))
+ doc-prop))
+
+(defun org-noter--other-frames (&optional this-frame)
+ "Return non-nil when there is at least another frame.
+This is called in `org-noter-kill-session'. THIS-FRAME can be
+specified to override `selected-frame'."
+ (setq this-frame (or this-frame (selected-frame)))
+ (catch 'other-frame
+ (dolist (frame (visible-frame-list))
+ (unless (or (eq this-frame frame)
+ (frame-parent frame)
+ (frame-parameter frame 'delete-before))
+ (throw 'other-frame frame)))))
+
+(defun org-noter--get-highlight-location ()
+ "Return a highlight location.
+This is mode specific. In PDF it's a the page number and 4
+coordinates for the highlight. This is delegated to each document
+mode."
+ (with-selected-window (org-noter--get-doc-window)
+ (run-hook-with-args-until-success 'org-noter--get-highlight-location-hook)))
+
+(defun org-noter--get-serialized-highlight (highlight-location)
+ "Return a string representation of the HIGHLIGHT-LOCATION.
+This is delegated to each document mode (eg pdf)."
+ (run-hook-with-args-until-success 'org-noter--pretty-print-highlight-location-hook highlight-location))
+
+(defun org-noter--update-doc-rename-in-notes (document-path new-document-path &optional _ok-if-already-exists)
+ "Update org-noter references to document-file whose name has changed.
+
+DOCUMENT-PATH is the original filename.
+NEW-DOCUMENT-PATH is the new filename.
+
+Call `org-noter-enable-sync-renames' to enable this feature and
+`org-noter-disable-sync-renames' to disable it.
+
+This advice runs after `dired-rename-file' completes successfully
+on files with `file-name-extension' in `org-noter--doc-extensions'.
+
+For notes files that have the same `file-name-base' as the
+document, the notes filename will be changed, but not its
+`file-name-directory'.
+
+If the document is moved to a path above the notes file, a
+warning will be issued, but the sync will proceed. The directory
+of the notes file will not be changed, as there may be other
+documents referenced in the notes file. An `org-noter' session
+can still be initiated from the notes file, but not vice-versa,
+nor will future renames of the document be synced in the notes
+file."
+
+ (when (and (file-name-extension document-path)
+ (member-ignore-case (file-name-extension document-path)
+ org-noter--doc-extensions)
+ (not (file-exists-p document-path))
+ (file-exists-p new-document-path))
+ ;; continue if the file extension is that of a document
+ ;; and the rename was successful
+ (let* ((document-name (file-name-nondirectory document-path))
+ (document-base (file-name-base document-name))
+ (document-directory (file-name-directory document-path))
+
+ (search-names (remove nil (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 ; list of notes files with promising names (Notes.org or .org)
+ notes-path) ; junk variable when iterating over notes-files
+
+ ;; find promising notes files by name in a few places...
+ (dolist (name search-names)
+ ;; check the notes-search-paths
+ (dolist (path org-noter-notes-search-path)
+ (setq notes-path (expand-file-name name path))
+ (when (file-exists-p notes-path)
+ (push notes-path notes-files)))
+ ;; check paths at or above document-directory
+ (let ((directory (locate-dominating-file document-directory name)))
+ (when directory
+ (setq notes-path (expand-file-name name directory))
+ (push notes-path notes-files))))
+
+ (setq notes-files (delete-dups notes-files))
+
+ ;; in each annotating notes file, find the entry for this file and update
+ ;; the document's relative path
+ (dolist (notes-path notes-files)
+ (when (org-noter--check-if-document-is-annotated-on-file document-path notes-path)
+ (with-temp-buffer
+ (insert-file-contents notes-path)
+ (org-with-point-at (point-min)
+ (catch 'break ;stop when we find a match
+ (while (re-search-forward (org-re-property org-noter-property-doc-file) nil)
+ (let ((property-value (match-string 3))
+ (notes-directory (file-name-directory notes-path)))
+ (when (string-equal (expand-file-name property-value notes-directory)
+ document-path)
+ (let ((doc-relative-name (file-relative-name new-document-path notes-directory))
+ msg)
+ ;; sync the new document path in this notes file
+ (org-set-property org-noter-property-doc-file doc-relative-name)
+ ;; warn against docs that reside above notes in path
+ (when (string-prefix-p "../" doc-relative-name)
+ (setq msg
+ (format-message "Document file has moved above notes file (%s). `org-noter' will not be able to find the notes file from the new document path (%s)." notes-path doc-relative-name))
+ (display-warning 'org-noter msg :warning)))
+ (write-file notes-path nil)
+ ;; change the notes filename if it was based on the document filename
+ (if (string-equal (file-name-base notes-path) document-base)
+ (let ((new-notes-path (concat (file-name-directory notes-path)
+ (file-name-base new-document-path) ".org")))
+ (rename-file notes-path new-notes-path)))
+ (throw 'break t))))))))))))
+
+(defun org-noter--update-notes-rename-in-notes (notes-path new-notes-path &optional _ok-if-already-exists)
+ "Update org-noter references to docs when notes file is moved.
+
+NOTES-PATH is the original filename.
+NEW-NOTES-PATH is the new filename.
+
+Call `org-noter-enable-sync-renames' to enable this feature and
+`org-noter-disable-sync-renames' to disable it.
+
+This advice runs after `dired-rename-file' moves an '.org' file to
+a different directory.
+
+If the notes file is moved to a path below any of its linked
+documents, a warning will be issued, but the sync will proceed.
+An `org-noter' session can still be initiated from the notes
+file, but not vice-versa, but future renames of the notes file
+will continue to sync the document references."
+
+ (when (and (string-equal (file-name-extension notes-path) "org")
+ (not (file-exists-p notes-path))
+ (file-exists-p new-notes-path)
+ (not (string-equal (file-name-directory notes-path)
+ (file-name-directory new-notes-path))))
+ ;; continue if it is an org file
+ ;; and the rename was successful
+ ;; and the directory changes
+ (let* (;;(document-name (file-name-nondirectory document-path))
+ ;;(document-base (file-name-base document-name))
+ ( notes-directory (file-name-directory notes-path))
+ (new-notes-directory (file-name-directory new-notes-path))
+ (problem-path-list nil)
+ (this-org-file-uses-noter nil))
+
+ ;; update each document's relative path
+ (with-temp-buffer
+ (insert-file-contents new-notes-path)
+ (org-with-point-at (point-min)
+ (while (re-search-forward (org-re-property org-noter-property-doc-file) nil t)
+ (let* (( doc-file-rel-path (match-string 3))
+ ( doc-file-abs-path (expand-file-name doc-file-rel-path notes-directory))
+ (new-doc-file-rel-path (file-relative-name doc-file-abs-path new-notes-directory)))
+ (setq this-org-file-uses-noter t)
+ ;; sync the document path to the new notes file
+ (org-set-property org-noter-property-doc-file new-doc-file-rel-path)
+ (next-line)
+ ;; add problematic paths to the list
+ (when (string-prefix-p "../" new-doc-file-rel-path)
+ (push new-doc-file-rel-path problem-path-list)))))
+ ;; warn against docs that reside above notes in path
+ (when problem-path-list
+ (let ((msg (format-message
+ "Notes file has moved below some documents. `org-noter' will not be able to find the notes file from the document path for these files:")))
+ (dolist (doc-path problem-path-list)
+ (setq msg (concat msg (format-message "\n%s" doc-path))))
+ (display-warning 'org-noter msg :warning)))
+ (when this-org-file-uses-noter
+ (write-file new-notes-path nil))))))
+
+;; --------------------------------------------------------------------------------
+;;; User commands
+(defun org-noter-set-start-location (&optional arg)
+ "When opening a session with this document, go to the current location.
+With a prefix ARG, remove start location."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let ((inhibit-read-only t)
+ (ast (org-noter--parse-root))
+ (location (org-noter--doc-approx-location
+ (when (called-interactively-p 'any) 'interactive))))
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if arg
+ (org-entry-delete nil org-noter-property-note-location)
+ (org-entry-put nil org-noter-property-note-location
+ (org-noter--pretty-print-location location))))))))
+
+(defun org-noter-set-auto-save-last-location (arg)
+ "Toggle saving the last visited location for this document.
+With a prefix ARG \\[universal-argument], delete the current
+setting and use the default."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let ((inhibit-read-only t)
+ (ast (org-noter--parse-root))
+ (new-setting (if arg
+ org-noter-auto-save-last-location
+ (not (org-noter--session-auto-save-last-location session)))))
+ (setf (org-noter--session-auto-save-last-location session)
+ new-setting)
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if arg
+ (org-entry-delete nil org-noter--property-auto-save-last-location)
+ (org-entry-put nil org-noter--property-auto-save-last-location (format "%s" new-setting)))
+ (unless new-setting (org-entry-delete nil org-noter-property-note-location)))))))
+
+(defun org-noter-set-hide-other (arg)
+ "Toggle hiding other headings for the current session.
+
+- With a prefix ARG \\[universal-argument], set the current setting
+ permanently for this document.
+
+- With a prefix ARG \\[universal-argument] \\[universal-argument],
+ remove the setting and use the default."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let* ((inhibit-read-only t)
+ (ast (org-noter--parse-root))
+ (persistent
+ (cond ((equal arg '(4)) 'write)
+ ((equal arg '(16)) 'remove)))
+ (new-setting
+ (cond ((eq persistent 'write) (org-noter--session-hide-other session))
+ ((eq persistent 'remove) org-noter-hide-other)
+ ('other-cases (not (org-noter--session-hide-other session))))))
+ (setf (org-noter--session-hide-other session) new-setting)
+ (when persistent
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if (eq persistent 'write)
+ (org-entry-put nil org-noter--property-hide-other (format "%s" new-setting))
+ (org-entry-delete nil org-noter--property-hide-other))))))))
+
+(defun org-noter-set-closest-tipping-point (arg)
+ "Set the closest note tipping point (see `org-noter-closest-tipping-point').
+
+- With a prefix ARG \\[universal-argument], set it permanently for
+ this document.
+
+- With a prefix ARG \\[universal-argument] \\[universal-argument],
+ remove the setting and use the default."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root))
+ (inhibit-read-only t)
+ (persistent (cond ((equal arg '(4)) 'write)
+ ((equal arg '(16)) 'remove)))
+ (new-setting (if (eq persistent 'remove)
+ org-noter-closest-tipping-point
+ (read-number "New tipping point: " (org-noter--session-closest-tipping-point session)))))
+ (setf (org-noter--session-closest-tipping-point session) new-setting)
+ (when persistent
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if (eq persistent 'write)
+ (org-entry-put nil org-noter--property-closest-tipping-point (format "%f" new-setting))
+ (org-entry-delete nil org-noter--property-closest-tipping-point))))))))
+
+(defun org-noter-set-notes-window-behavior (arg)
+ "Set the notes window behaviour for the current session.
+With a prefix ARG, it becomes persistent for that document.
+
+See `org-noter-notes-window-behavior' for more information."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let* ((inhibit-read-only t)
+ (ast (org-noter--parse-root))
+ (possible-behaviors (list '("Default" . default)
+ '("On start" . start)
+ '("On scroll" . scroll)
+ '("On scroll to location that only has previous notes" . only-prev)
+ '("Never" . never)))
+ chosen-behaviors)
+
+ (while (> (length possible-behaviors) 1)
+ (let ((chosen-pair (assoc (completing-read "Behavior: " possible-behaviors nil t) possible-behaviors)))
+ (cond ((eq (cdr chosen-pair) 'default) (setq possible-behaviors nil))
+
+ ((eq (cdr chosen-pair) 'never) (setq chosen-behaviors (list 'never)
+ possible-behaviors nil))
+
+ ((eq (cdr chosen-pair) 'done) (setq possible-behaviors nil))
+
+ (t (push (cdr chosen-pair) chosen-behaviors)
+ (setq possible-behaviors (delq chosen-pair possible-behaviors))
+ (when (= (length chosen-behaviors) 1)
+ (setq possible-behaviors (delq (rassq 'default possible-behaviors) possible-behaviors)
+ possible-behaviors (delq (rassq 'never possible-behaviors) possible-behaviors))
+ (push (cons "Done" 'done) possible-behaviors))))))
+
+ (setf (org-noter--session-window-behavior session)
+ (or chosen-behaviors org-noter-notes-window-behavior))
+
+ (when arg
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if chosen-behaviors
+ (org-entry-put nil org-noter--property-behavior (format "%s" chosen-behaviors))
+ (org-entry-delete nil org-noter--property-behavior))))))))
+
+(defun org-noter-toggle-notes-window-location ()
+ "Toggle between side- and bottom-notes window location.
+Only acts on the current session."
+ (interactive)
+ (org-noter--with-valid-session
+ (let ((current-notes-location (org-noter--session-window-location session))
+ (notes-buffer (org-noter--session-notes-buffer session)))
+ (cond ((eq current-notes-location 'horizontal-split)
+ (setf (org-noter--session-window-location session) 'vertical-split))
+ ((eq current-notes-location 'vertical-split)
+ (setf (org-noter--session-window-location session) 'horizontal-split)))
+ (org-noter--relocate-notes-window notes-buffer))))
+
+(defun org-noter-set-notes-window-location (arg)
+ "Set the notes window default location for the current session.
+With a prefix ARG, it becomes persistent for that document.
+
+See `org-noter-notes-window-behavior' for more information."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let* ((inhibit-read-only t)
+ (ast (org-noter--parse-root))
+ (location-possibilities
+ '(("Default" . nil)
+ ("Horizontal split" . horizontal-split)
+ ("Vertical split" . vertical-split)
+ ("Other frame" . other-frame)))
+ (location
+ (cdr (assoc (completing-read "Location: " location-possibilities nil t)
+ location-possibilities)))
+ (notes-buffer (org-noter--session-notes-buffer session)))
+
+ (setf (org-noter--session-window-location session)
+ (or location org-noter-notes-window-location))
+ (org-noter--relocate-notes-window notes-buffer)
+
+ (when arg
+ (with-current-buffer notes-buffer
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if location
+ (org-entry-put nil org-noter--property-location
+ (format "%s" location))
+ (org-entry-delete nil org-noter--property-location))))))))
+
+(defun org-noter-set-doc-split-fraction (arg)
+ "Set the fraction of the frame that the document window will occupy when split.
+
+- With a prefix ARG \\[universal-argument], set it permanently
+ for this document.
+
+- With a prefix ARG \\[universal-argument]
+ \\[universal-argument], remove the setting and use the
+ default."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root))
+ (inhibit-read-only t)
+ (persistent (cond ((equal arg '(4)) 'write)
+ ((equal arg '(16)) 'remove)))
+ (current-setting (org-noter--session-doc-split-fraction session))
+ (new-setting
+ (if (eq persistent 'remove)
+ org-noter-doc-split-fraction
+ (cons (read-number "Horizontal fraction: " (car current-setting))
+ (read-number "Vertical fraction: " (cdr current-setting))))))
+ (setf (org-noter--session-doc-split-fraction session) new-setting)
+ (when (org-noter--get-notes-window)
+ (with-current-buffer (org-noter--session-doc-buffer session)
+ (delete-other-windows)
+ (org-noter--get-notes-window 'force)))
+
+ (when persistent
+ (with-current-buffer (org-noter--session-notes-buffer session)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :begin ast))
+ (if (eq persistent 'write)
+ (org-entry-put nil org-noter--property-doc-split-fraction (format "%s" new-setting))
+ (org-entry-delete nil org-noter--property-doc-split-fraction))))))))
+
+(defun org-noter-kill-session (&optional session)
+ "Kill an `org-noter' session.
+
+When called interactively, if there is no prefix argument and the
+buffer has an annotation session, it will kill it; else, it will
+show a list of open `org-noter' sessions, asking for which to
+kill.
+
+When called from elisp code, you have to pass in the SESSION you
+want to kill."
+ (interactive "P")
+ (when (and (called-interactively-p 'any) (> (length org-noter--sessions) 0))
+ ;; NOTE(nox): `session' is representing a prefix argument
+ (if (and org-noter--session (not session))
+ (setq session org-noter--session)
+ (setq session nil)
+ (let (collection default doc-display-name notes-file-name display)
+ (dolist (session org-noter--sessions)
+ (setq doc-display-name (org-noter--session-display-name session)
+ notes-file-name (file-name-nondirectory
+ (org-noter--session-notes-file-path session))
+ display (concat doc-display-name " - " notes-file-name))
+ (when (eq session org-noter--session) (setq default display))
+ (push (cons display session) collection))
+ (setq session (cdr (assoc (completing-read "Which session? " collection nil t
+ nil nil default)
+ collection))))))
+
+ (when (and session (memq session org-noter--sessions))
+ (setq org-noter--sessions (delq session org-noter--sessions))
+
+ (when (eq (length org-noter--sessions) 0)
+ (remove-hook 'delete-frame-functions 'org-noter--handle-delete-frame)
+ (run-hooks 'org-noter--no-sessions-remove-advice-hooks))
+
+ (let* ((ast (org-noter--parse-root session))
+ (frame (org-noter--session-frame session))
+ (notes-buffer (org-noter--session-notes-buffer session))
+ (base-buffer (buffer-base-buffer notes-buffer))
+ (notes-modified (buffer-modified-p base-buffer))
+ (doc-buffer (org-noter--session-doc-buffer session)))
+
+ (dolist (window (get-buffer-window-list notes-buffer nil t))
+ (with-selected-frame (window-frame window)
+ (if (= (count-windows) 1)
+ (when (org-noter--other-frames) (delete-frame))
+ (delete-window window))))
+
+ (with-current-buffer notes-buffer
+ (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t)
+ (restore-buffer-modified-p nil))
+ (when org-noter-use-indirect-buffer
+ (kill-buffer notes-buffer))
+
+ (when base-buffer
+ (with-current-buffer base-buffer
+ (org-noter--unset-text-properties ast)
+ (set-buffer-modified-p notes-modified)))
+
+ (with-current-buffer doc-buffer
+ (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t))
+ (unless org-noter-kill-frame-at-session-end
+ (set-window-dedicated-p (get-buffer-window doc-buffer) nil))
+ (kill-buffer doc-buffer)
+
+ (when (frame-live-p frame)
+ (if (and (org-noter--other-frames) org-noter-kill-frame-at-session-end)
+ (delete-frame frame)
+ (progn
+ (delete-other-windows)
+ (set-frame-parameter nil 'name nil)))))))
+
+(defun org-noter-create-skeleton ()
+ "Create notes skeleton based on the outline of the document."
+ (interactive)
+ (org-noter--with-valid-session
+ (or (run-hook-with-args-until-success 'org-noter-create-skeleton-functions
+ (org-noter--session-doc-mode session))
+ (user-error "This command is not supported for %s"
+ (org-noter--session-doc-mode session)))))
+
+(defun org-noter-insert-note (&optional toggle-highlight precise-info)
+ "Insert note associated with the current location.
+
+This command will prompt for a title of the note and then insert
+it in the notes buffer. When the input is empty, a title based on
+either the selected text (if it is <=
+`org-noter-max-short-selected-text-length') or
+`org-noter-default-heading-title' will be generated.
+
+If there are other notes related to the current location, the
+prompt will also suggest them. Depending on the value of the
+variable `org-noter-closest-tipping-point', it may also suggest
+the closest previous note.
+
+The prefix \\[universal-argument] sets TOGGLE-HIGHLIGHT, which
+inverts the logic of the custom variable
+`org-noter-highlight-selected-text' for this note.
+
+PRECISE-INFO makes the new note associated with a more specific
+location (see `org-noter-insert-precise-note' for more info).
+
+When you insert into an existing note and have text selected on
+the document buffer, the variable
+`org-noter-insert-selected-text-inside-note' defines if the text
+should be inserted inside the note.
+
+Guiding principles for note generation
+ 1. The preferred title is the one the user enters in the
+ minibuffer.
+ 2. Selected text should be used in the note, either as the
+ title or in the body
+ 3. Refrain from making notes in the same location with the same
+ title
+ 4. Precise notes generally have different locations, so always
+ make new precise notes"
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root)) (contents (org-element-contents ast))
+ (window (org-noter--get-notes-window 'force))
+ (selected-text (run-hook-with-args-until-success
+ 'org-noter-get-selected-text-hook
+ (org-noter--session-doc-mode session)))
+ (selected-text-p (> (length selected-text) 0))
+ force-new
+ (location (org-noter--doc-approx-location (or precise-info 'interactive) (gv-ref force-new)))
+ (current-view (org-noter--get-current-view)))
+
+ (let* ((inhibit-quit t)
+ (short-selected-text (if (and selected-text-p
+ (<= (length selected-text) org-noter-max-short-selected-text-length))
+ selected-text))
+ (org-noter-highlight-selected-text (if toggle-highlight (not org-noter-highlight-selected-text)
+ org-noter-highlight-selected-text))
+ (highlight-location (if org-noter-highlight-selected-text (org-noter--get-highlight-location))))
+
+ (with-local-quit
+ (select-frame-set-input-focus (window-frame window))
+ (select-window window)
+
+ ;; IMPORTANT(nox): Need to be careful changing the next part, it is a bit
+ ;; complicated to get it right...
+
+ (let ((view-info (org-noter--get-view-info current-view location))
+ (minibuffer-local-completion-map org-noter--completing-read-keymap)
+ collection title note-body existing-note
+ (default-title (or short-selected-text
+ (replace-regexp-in-string (regexp-quote "$p$")
+ (org-noter--pretty-print-location-for-title location)
+ org-noter-default-heading-title)))
+ (empty-lines-number (if org-noter-separate-notes-from-heading 2 1)))
+
+ ;; NOTE(phm): prompt for title unless this is a precise note
+ (unless precise-info
+ ;; construct collection for matching existing notes
+ (dolist (note-cons (org-noter--view-info-notes view-info))
+ (let ((display (org-element-property :raw-value (car note-cons))))
+ (push (cons display note-cons) collection))))
+
+ (setq collection (nreverse collection)
+ ;; prompt for title (unless no-Q's)
+ title (if org-noter-insert-note-no-questions default-title
+ (completing-read "Note: " collection nil nil nil nil default-title))
+ note-body (if (and selected-text-p
+ (not (equal title short-selected-text)))
+ selected-text)
+ ;; is this an existing note? skip for precise notes
+ existing-note (unless precise-info (cdr (assoc title collection))))
+
+ (if existing-note
+ ;; NOTE(nox): Inserting on an existing note
+ (let* ((note (car existing-note))
+ (insert-before-element (cdr existing-note))
+ (has-content
+ (eq (org-element-map (org-element-contents note) org-element-all-elements
+ (lambda (element)
+ (if (org-noter--check-location-property element)
+ 'stop
+ (not (memq (org-element-type element) '(section property-drawer)))))
+ nil t)
+ t)))
+ (when has-content (setq empty-lines-number 2))
+ (if insert-before-element
+ (goto-char (org-element-property :begin insert-before-element))
+ (goto-char (org-element-property :end note)))
+
+ (if (org-at-heading-p)
+ (progn
+ (org-N-empty-lines-before-current empty-lines-number)
+ (forward-line -1))
+ (unless (bolp) (insert "\n"))
+ (org-N-empty-lines-before-current (1- empty-lines-number)))
+
+ (when (and org-noter-insert-selected-text-inside-note note-body)
+ (if short-selected-text
+ (insert "``" note-body "''")
+ (insert "#+BEGIN_QUOTE\n" note-body "\n#+END_QUOTE"))))
+
+ ;; NOTE(nox): Inserting a new note
+ (let ((reference-element-cons (org-noter--view-info-reference-for-insertion view-info))
+ level)
+ (if reference-element-cons
+ (progn
+ (cond
+ ((eq (car reference-element-cons) 'before)
+ (goto-char (org-element-property :begin (cdr reference-element-cons))))
+ ((eq (car reference-element-cons) 'after)
+ (goto-char (org-element-property :end (cdr reference-element-cons)))))
+
+ ;; NOTE(nox): This is here to make the automatic "should insert blank" work better.
+ (when (org-at-heading-p) (backward-char))
+
+ (setq level (org-element-property :level (cdr reference-element-cons))))
+
+ (goto-char (or (org-element-map contents 'section
+ (lambda (section) (org-element-property :end section))
+ nil t org-element-all-elements)
+ (point-max))))
+
+ (setq level (or level
+ (1+ (or (org-element-property :level ast) 0))))
+
+ ;; NOTE(nox): This is needed to insert in the right place
+ (unless (org-noter--no-heading-p) (outline-show-entry))
+ (org-noter--insert-heading level title empty-lines-number location)
+ ;; store the highlight in org IF we have a highlight AND can serialize it.
+ (when-let ((highlight-location)
+ (serialized-highlight (org-noter--get-serialized-highlight highlight-location)))
+ (org-set-property "HIGHLIGHT" serialized-highlight))
+ (when note-body
+ (save-excursion
+ (if short-selected-text
+ (insert "``" note-body "''")
+ (insert "#+BEGIN_QUOTE\n" note-body "\n#+END_QUOTE"))))
+ (when (org-noter--session-hide-other session) (org-overview))
+
+ (setf (org-noter--session-num-notes-in-view session)
+ (1+ (org-noter--session-num-notes-in-view session)))))
+
+ (org-show-set-visibility t)
+ (org-cycle-hide-drawers 'all)
+ (org-cycle-show-empty-lines t)
+ (when org-noter-highlight-selected-text ; return to DOC window and highlight text
+ (select-frame-set-input-focus (org-noter--session-frame session))
+ (select-window (get-buffer-window (org-noter--session-doc-buffer session)))
+ (run-hook-with-args-until-success 'org-noter--add-highlight-hook major-mode highlight-location))))
+ (when quit-flag
+ ;; NOTE(nox): If this runs, it means the user quitted while creating a note, so
+ ;; revert to the previous window.
+ (select-frame-set-input-focus (org-noter--session-frame session))
+ (select-window (get-buffer-window (org-noter--session-doc-buffer session))))))))
+
+(defun org-noter-insert-precise-note (&optional toggle-highlight)
+ "Insert note associated with a specific location.
+This will ask you to click where you want to scroll to when you
+sync the document to this note. You should click on the top of
+that part. Will always create a new note.
+
+When text is selected, it will automatically choose the top of
+the selected text as the location and the text itself as the
+default title of the note if the text does not exceed
+`org-noter-max-short-selected-text-length'.
+
+Use prefix [\\universal-argument] to TOGGLE-HIGHLIGHT.
+
+See `org-noter-insert-note' docstring for more."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let ((precise-info (org-noter--get-precise-info)))
+ (org-noter-insert-note toggle-highlight precise-info))))
+
+(defun org-noter-insert-note-toggle-no-questions (&optional toggle-highlight)
+ "Insert note associated with the current location.
+This is like `org-noter-insert-note', except it will toggle
+`org-noter-insert-note-no-questions'.
+
+Use prefix [\\universal-argument] to TOGGLE-HIGHLIGHT."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let ((org-noter-insert-note-no-questions (not org-noter-insert-note-no-questions)))
+ (org-noter-insert-note toggle-highlight))))
+
+(defun org-noter-insert-precise-note-toggle-no-questions (&optional toggle-highlight)
+ "Insert note associated with the current location.
+This is like `org-noter-insert-precise-note', except it will
+toggle `org-noter-insert-note-no-questions'.
+
+Use prefix [\\universal-argument] to TOGGLE-HIGHLIGHT."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let ((org-noter-insert-note-no-questions (not org-noter-insert-note-no-questions)))
+ (org-noter-insert-precise-note toggle-highlight))))
+
+(defmacro org-noter--map-ignore-headings-with-doc-file (contents match-first &rest body)
+ `(let (ignore-until-level)
+ (org-element-map ,contents 'headline
+ (lambda (headline)
+ (let ((doc-file (org-noter--doc-file-property headline))
+ (location (org-noter--parse-location-property headline)))
+ (when (and ignore-until-level (<= (org-element-property :level headline) ignore-until-level))
+ (setq ignore-until-level nil))
+
+ (cond
+ (ignore-until-level nil) ;; NOTE(nox): This heading is ignored, do nothing
+ ((and doc-file (not (string= doc-file (org-noter--session-property-text session))))
+ (setq ignore-until-level (org-element-property :level headline)) nil)
+ (t ,@body))))
+ nil ,match-first org-noter--note-search-no-recurse)))
+
+(defun org-noter-sync-prev-page-or-chapter ()
+ "Show previous page or chapter that has notes.
+This command navigates in relation to the current page or chapter
+of the document. This will force the notes window to popup."
+ (interactive)
+ (org-noter--with-valid-session
+ (let ((this-location (org-noter--doc-approx-location 0))
+ (contents (org-element-contents (org-noter--parse-root)))
+ target-location)
+ (org-noter--get-notes-window 'force)
+
+ (org-noter--map-ignore-headings-with-doc-file
+ contents nil
+ (when (and (org-noter--compare-locations '< location this-location)
+ (org-noter--compare-locations '>f location target-location))
+ (setq target-location location)))
+
+ (org-noter--get-notes-window 'force)
+ (select-window (org-noter--get-doc-window))
+ (if target-location
+ (org-noter--doc-goto-location target-location)
+ (user-error "There are no more previous pages or chapters with notes")))))
+
+(defun org-noter-sync-current-page-or-chapter ()
+ "Show current page or chapter notes.
+This will force the notes window to popup."
+ (interactive)
+ (org-noter--with-valid-session
+ (let ((window (org-noter--get-notes-window 'force)))
+ (select-frame-set-input-focus (window-frame window))
+ (select-window window)
+ (org-noter--doc-location-change-handler))))
+
+(defun org-noter-sync-next-page-or-chapter ()
+ "Show next page or chapter that has notes.
+This command navigates in relation to the current page or chapter
+of the document. This will force the notes window to popup."
+ (interactive)
+ (org-noter--with-valid-session
+ (let ((this-location (org-noter--doc-approx-location most-positive-fixnum))
+ (contents (org-element-contents (org-noter--parse-root)))
+ target-location)
+
+ (org-noter--map-ignore-headings-with-doc-file
+ contents t
+ (when (and (org-noter--compare-locations '> location this-location)
+ (org-noter--compare-locations '< location target-location))
+ (setq target-location location)))
+
+ (org-noter--get-notes-window 'force)
+ (select-window (org-noter--get-doc-window))
+ (if target-location
+ (org-noter--doc-goto-location target-location)
+ (user-error "There are no more following pages or chapters with notes")))))
+
+(defun org-noter-sync-prev-note ()
+ "Go to the location of the previous note, in relation to where the point is.
+As such, it will only work when the notes window exists."
+ (interactive)
+ (org-noter--with-selected-notes-window
+ "No notes window exists"
+ (let ((org-noter--inhibit-location-change-handler t)
+ (contents (org-element-contents (org-noter--parse-root)))
+ (current-begin (org-element-property :begin (org-noter--get-containing-element)))
+ previous)
+ (when current-begin
+ (org-noter--map-ignore-headings-with-doc-file
+ contents t
+ (when location
+ (if (= current-begin (org-element-property :begin headline))
+ t
+ (setq previous headline)
+ nil))))
+
+ (if previous
+ (progn
+ ;; NOTE(nox): This needs to be manual so we can focus the correct note
+ (org-noter--doc-goto-location (org-noter--parse-location-property previous))
+ (org-noter--focus-notes-region (org-noter--make-view-info-for-single-note session previous)))
+ (user-error "There is no previous note"))))
+ (select-window (org-noter--get-doc-window)))
+
+(defun org-noter-sync-current-note ()
+ "Go the location of the selected note, in relation to where the point is.
+As such, it will only work when the notes window exists."
+ (interactive)
+ (org-noter--with-selected-notes-window
+ "No notes window exists"
+ (if (string= (org-noter--get-or-read-document-property t)
+ (org-noter--session-property-text session))
+ (let ((location (org-noter--parse-location-property (org-noter--get-containing-element))))
+ (if location
+ (org-noter--doc-goto-location location)
+ (user-error "No note selected")))
+ (user-error "You are inside a different document")))
+ (let ((window (org-noter--get-doc-window)))
+ (select-frame-set-input-focus (window-frame window))
+ (select-window window)))
+
+(defun org-noter-sync-next-note ()
+ "Go to the location of the next note, in relation to where the point is.
+As such, it will only work when the notes window exists."
+ (interactive)
+ (org-noter--with-selected-notes-window
+ "No notes window exists"
+ (let ((org-noter--inhibit-location-change-handler t)
+ (contents (org-element-contents (org-noter--parse-root)))
+ next)
+
+ (org-noter--map-ignore-headings-with-doc-file
+ contents t
+ (when (and location (< (point) (org-element-property :begin headline)))
+ (setq next headline)))
+
+ (if next
+ (progn
+ (org-noter--doc-goto-location (org-noter--parse-location-property next))
+ (org-noter--focus-notes-region (org-noter--make-view-info-for-single-note session next)))
+ (user-error "There is no next note"))))
+ (select-window (org-noter--get-doc-window)))
+
+(defun org-noter-enable-update-renames ()
+ "Enable `dired-rename-file' advice for moving docs and notes.
+Enables `org-noter--update-doc-rename-in-notes' and
+`org-noter--update-notes-rename-in-notes' as advice :after
+`dired-rename-file'.
+
+In dired, this affects the renaming of supported document files
+and .org files.
+
+This feature can be turn off with `org-noter-disable-sync-renames'."
+ (interactive)
+ (advice-add 'dired-rename-file :after 'org-noter--update-doc-rename-in-notes)
+ (advice-add 'dired-rename-file :after 'org-noter--update-notes-rename-in-notes))
+
+(defun org-noter-disable-update-renames ()
+ "Disable `dired-rename-file' advice for moving docs and notes.
+Run this if you change your mind about using the rename
+synchronization features."
+ (interactive)
+ (advice-remove 'dired-rename-file 'org-noter--update-doc-rename-in-notes)
+ (advice-remove 'dired-rename-file 'org-noter--update-notes-rename-in-notes))
+
+(define-minor-mode org-noter-doc-mode
+ "Minor mode for the document buffer.
+Keymap:
+\\{org-noter-doc-mode-map}"
+ :keymap `((,(kbd "i") . org-noter-insert-note)
+ (,(kbd "C-i") . org-noter-insert-note-toggle-no-questions)
+ (,(kbd "M-i") . org-noter-insert-precise-note)
+ (,(kbd "C-M-i") . org-noter-insert-precise-note-toggle-no-questions)
+ (,(kbd "q") . org-noter-kill-session)
+ (,(kbd "M-p") . org-noter-sync-prev-page-or-chapter)
+ (,(kbd "M-.") . org-noter-sync-current-page-or-chapter)
+ (,(kbd "M-n") . org-noter-sync-next-page-or-chapter)
+ (,(kbd "C-M-p") . org-noter-sync-prev-note)
+ (,(kbd "C-M-.") . org-noter-sync-current-note)
+ (,(kbd "C-M-n") . org-noter-sync-next-note)
+ (,(kbd "M-T") . org-noter-toggle-notes-window-location))
+
+ (let ((mode-line-segment '(:eval (org-noter--mode-line-text))))
+ (if org-noter-doc-mode
+ (if (symbolp (car-safe mode-line-format))
+ (setq mode-line-format (list mode-line-segment mode-line-format))
+ (push mode-line-segment mode-line-format))
+ (setq mode-line-format (delete mode-line-segment mode-line-format)))))
+
+(define-minor-mode org-noter-notes-mode
+ "Minor mode for the notes buffer.
+Keymap:
+\\{org-noter-notes-mode-map}"
+ :keymap `((,(kbd "M-p") . org-noter-sync-prev-page-or-chapter)
+ (,(kbd "M-.") . org-noter-sync-current-page-or-chapter)
+ (,(kbd "M-n") . org-noter-sync-next-page-or-chapter)
+ (,(kbd "C-M-p") . org-noter-sync-prev-note)
+ (,(kbd "C-M-.") . org-noter-sync-current-note)
+ (,(kbd "C-M-n") . org-noter-sync-next-note)
+ (,(kbd "M-T") . org-noter-toggle-notes-window-location))
+ (if org-noter-doc-mode
+ (org-noter-doc-mode -1)))
+
+(provide 'org-noter-core)
+;;; org-noter-core.el ends here
diff --git a/org-noter-test-utils.el b/org-noter-test-utils.el
new file mode 100644
index 0000000..231bc99
--- /dev/null
+++ b/org-noter-test-utils.el
@@ -0,0 +1,178 @@
+;; we need to load undecover before all the other org-noter modules so that undercover can instrument code to generate test coverage.
+(when (require 'undercover nil t)
+ (setq undercover-force-coverage t)
+
+ (message "Enable test coverage.")
+ (undercover "*.el" "modules/*.el"
+ (:exclude "org-noter-test-utils.el")
+ (:report-format 'lcov)
+ (:send-report nil)))
+
+
+(require 'log4e)
+(add-to-list 'load-path "modules")
+(require 'org-noter)
+(require 'with-simulated-input)
+
+
+(message "Emacs version: %s" (version))
+
+
+
+
+
+;; org-noter-test logger = ont
+(log4e:deflogger "ont" "ont %t [%l] %m" "%H:%M:%S")
+(ont--log-enable-logging)
+(ont--log-enable-debugging)
+(ont--log-enable-messaging)
+(ont--log-set-level 'info)
+(ont--log-debug "ont")
+
+
+(defvar mock-contents-simple-notes-file
+ "
+:PROPERTIES:
+:ID: FAKE_1
+:END:
+#+TITLE: Test book notes (simple)
+* solove-nothing-to-hide
+:PROPERTIES:
+:NOTER_DOCUMENT: /tmp/test.pdf
+:END:
+")
+
+(defvar mock-contents-simple-notes-file-with-a-single-note
+ ":PROPERTIES:
+:ID: FAKE_90283
+:END:
+#+TITLE: Test book notes
+
+* solove-nothing-to-hide
+:PROPERTIES:
+:NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
+:END:
+** Note from page 1
+:PROPERTIES:
+:NOTER_PAGE: 99
+:END:
+"
+)
+
+
+
+
+;;;;;;;;;;;
+;; helpers
+(defun org-noter-core-test-create-session ()
+ "Call this manually with an existing notes buffer to generate a new session"
+ (org-noter--create-session (org-noter--parse-root) "pubs/solove-nothing-to-hide.pdf" org-noter-test-file))
+
+
+(defun with-mock-contents (contents lambda)
+ "Create a real buffer with CONTENTS and then execute the LAMBDA"
+ (ont--log-debug "\n--------------------------------------------")
+
+ ;; TODO: when an assert fails in buttercup, an exception (??) is thrown,
+ ;; so temp file isnt being cleaned up. This is the sledgehammer approach.
+ ;; Needs to be fixed so that it's cleaned up properly.
+ (when (boundp 'org-noter-test-file)
+ (progn
+ (ont--log-debug (format "Removing org-noter-test-file: %s\n" org-noter-test-file))
+ (delete-file org-noter-test-file)))
+ (let* ((tempfile (make-temp-file "Notes" nil ".org" contents)))
+ (ont--log-debug (format "Creating a tempfile: %s\n" tempfile))
+ (setq org-noter-test-file tempfile)
+ (ont--log-debug "Opening the file..")
+ (org-mode)
+ (find-file tempfile)
+ (org-mode)
+ (ont--log-debug "Starting the test..")
+ (ont--log-debug "%s" (buffer-string))
+ (funcall lambda)
+ (ont--log-debug "About to kill buffer..")
+ (kill-current-buffer)
+ (ont--log-debug (format "Removing tempfile %s" tempfile))
+ (delete-file tempfile)
+ (ont--log-debug "+++++++++++++++++++++++++++++++++++++++++")
+ ))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; hooks - org-noter calls these
+
+(defun org-noter-test-get-selected-text (mode)
+ "⚠️org-noter-core-test-return-text
+org-noter-core-test-return-text
+org-noter-core-test-return-text
+org-noter-core-test-return-text
+org-noter-core-test-return-text
+")
+
+
+(defun org-noter-core-test-document-property (&optional param)
+ org-noter-test-file)
+
+(defun org-noter-core-test-view-setup-handler (&optional param)
+ t)
+
+(defun org-noter-core-test-open-document-functions (&optional doc)
+ (find-file (org-noter-core-test-document-property)))
+
+(defun org-noter-core-test-approx-location (major-mode &optional precise-info _force-new-ref)
+ (cons 99 precise-info))
+
+(defun org-noter-core-test-get-current-view (mode)
+ t)
+
+;; TODO This doesn't look right
+(defun org-noter-core-test-get-precise-info (mode window)
+ (list 1 2 3 4))
+
+(defun org-noter-core-test-pretty-print-location (location)
+ (format "%s" location))
+
+(defun org-noter-core-test-add-highlight (major-mode precise-info)
+ t)
+
+(defun org-noter-core-test-get-current-view (mode)
+ 'org-noter-core-test-view)
+
+(defun org-noter-core-test-get-highlight-location ()
+ "HARDCODED_HIGHLIGHT_LOCATION")
+
+(defun org-noter-core-test-pretty-print-location-for-title (location)
+ "TEST PRETTY PRINT LOCATION")
+
+(defun create-org-noter-test-session ()
+
+ ;; if this is not set; make-session fails and the test crashes with a stack overflow.
+ (setq org-noter-always-create-frame nil)
+
+ (setq org-noter-highlight-selected-text t)
+
+ ;; setup spies so we can verify that things have been called
+ (spy-on 'org-noter-test-get-selected-text :and-call-through)
+ (spy-on 'org-noter-core-test-approx-location :and-call-through)
+ (spy-on 'org-noter-core-test-get-precise-info :and-call-through)
+ (spy-on 'org-noter-core-test-add-highlight :and-call-through)
+ (spy-on 'org-noter-core-test-get-current-view :and-call-through)
+
+ ;; register all the hooks so we can fake a org-noter-test mode
+ (add-to-list 'org-noter-get-selected-text-hook #'org-noter-test-get-selected-text)
+ (add-to-list 'org-noter-parse-document-property-hook #'org-noter-core-test-document-property)
+ (add-to-list 'org-noter-set-up-document-hook #'org-noter-core-test-view-setup-handler)
+ (add-to-list 'org-noter-open-document-functions #'org-noter-core-test-open-document-functions)
+ (add-to-list 'org-noter--doc-approx-location-hook #'org-noter-core-test-approx-location)
+ (add-to-list 'org-noter--get-current-view-hook #'org-noter-core-test-get-current-view)
+ (add-to-list 'org-noter--get-precise-info-hook #'org-noter-core-test-get-precise-info)
+ (add-to-list 'org-noter--pretty-print-location-hook #'org-noter-core-test-pretty-print-location)
+ (add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-core-test-pretty-print-location-for-title)
+ (add-to-list 'org-noter--add-highlight-hook #'org-noter-core-test-add-highlight)
+ (add-to-list 'org-noter--get-highlight-location-hook #'org-noter-core-test-get-highlight-location)
+ )
+
+
+
+
+(provide 'org-noter-test-utils)
diff --git a/org-noter.el b/org-noter.el
index 129d61c..6e15d4c 100644
--- a/org-noter.el
+++ b/org-noter.el
@@ -1,12 +1,15 @@
;;; org-noter.el --- A synchronized, Org-mode, document annotator -*- lexical-binding: t; -*-
-;; Copyright (C) 2017-2018 Gonçalo Santos
+;; Copyright (C) 2017-2019 Gonçalo Santos
-;; Author: Gonçalo Santos (aka. weirdNox@GitHub)
-;; Homepage: https://github.com/weirdNox/org-noter
+;; Author: Gonçalo Santos (github.com/weirdNox)
+;; Maintainer Dmitry M
+;; Maintainer: Peter Mao
+;; Dmitry M
+;; Homepage: https://github.com/org-noter/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
+;; Version: 1.5.0
;; This file is not part of GNU Emacs.
@@ -25,2075 +28,51 @@
;;; 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.
+;; 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)
(require 'org-element)
(require 'cl-lib)
-(declare-function doc-view-goto-page "doc-view")
-(declare-function image-display-size "image-mode")
-(declare-function image-get-display-property "image-mode")
-(declare-function image-mode-window-get "image-mode")
-(declare-function image-scroll-up "image-mode")
-(declare-function nov-render-document "ext:nov")
-(declare-function org-attach-dir "org-attach")
-(declare-function org-attach-file-list "org-attach")
-(declare-function pdf-info-getannots "ext:pdf-info")
-(declare-function pdf-info-gettext "ext:pdf-info")
-(declare-function pdf-info-outline "ext:pdf-info")
-(declare-function pdf-info-pagelinks "ext:pdf-info")
-(declare-function pdf-util-tooltip-arrow "ext:pdf-util")
-(declare-function pdf-view-active-region "ext:pdf-view")
-(declare-function pdf-view-active-region-p "ext:pdf-view")
-(declare-function pdf-view-active-region-text "ext:pdf-view")
-(declare-function pdf-view-goto-page "ext:pdf-view")
-(declare-function pdf-view-mode "ext:pdf-view")
-(defvar nov-documents-index)
-(defvar nov-file-name)
+(require 'org-noter-core)
-;; --------------------------------------------------------------------------------
-;; NOTE(nox): User variables
-(defgroup org-noter nil
- "A synchronized, external annotator"
- :group 'convenience
- :version "25.3.1")
+(declare-function org-entry-put "org")
+(declare-function org-with-wide-buffer "org-macs")
-(defcustom org-noter-property-doc-file "NOTER_DOCUMENT"
- "Name of the property that specifies the document."
- :group 'org-noter
- :type 'string)
-
-(defcustom org-noter-property-note-location "NOTER_PAGE"
- "Name of the property that specifies the location of the current note.
-The default value is still NOTER_PAGE for backwards compatibility."
- :group 'org-noter
- :type 'string)
-
-(defcustom org-noter-default-heading-title "Notes for page $p$"
- "The default title for headings created with `org-noter-insert-note'.
-$p$ is replaced with the number of the page or chapter you are in
-at the moment."
- :group 'org-noter
- :type 'string)
-
-(defcustom org-noter-notes-window-behavior '(start scroll)
- "This setting specifies in what situations the notes window should be created.
-
-When the list contains:
-- `start', the window will be created when starting a `org-noter' session.
-- `scroll', it will be created when you go to a location with an associated note.
-- `only-prev', it will be created when you go to a location without notes, but that
- has previous notes that are shown."
- :group 'org-noter
- :type '(set (const :tag "Session start" start)
- (const :tag "Scroll to location with notes" scroll)
- (const :tag "Scroll to location with previous notes only" only-prev)))
-
-(defcustom org-noter-notes-window-location 'horizontal-split
- "Whether the notes should appear in the main frame (horizontal or vertical split) or in a separate frame.
-
-Note that this will only have effect on session startup if `start'
-is member of `org-noter-notes-window-behavior' (which see)."
- :group 'org-noter
- :type '(choice (const :tag "Horizontal" horizontal-split)
- (const :tag "Vertical" vertical-split)
- (const :tag "Other frame" other-frame)))
-
-(define-obsolete-variable-alias 'org-noter-doc-split-percentage 'org-noter-doc-split-fraction "1.2.0")
-(defcustom org-noter-doc-split-fraction '(0.5 . 0.5)
- "Fraction of the frame that the document window will occupy when split.
-This is a cons of the type (HORIZONTAL-FRACTION . VERTICAL-FRACTION)."
- :group 'org-noter
- :type '(cons (number :tag "Horizontal fraction") (number :tag "Vertical fraction")))
-
-(defcustom org-noter-auto-save-last-location nil
- "When non-nil, save the last visited location automatically; when starting a new session, go to that location."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-hide-other t
- "When non-nil, hide all headings not related to the command used.
-For example, when scrolling to pages with notes, collapse all the
-notes that are not annotating the current page."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-always-create-frame t
- "When non-nil, org-noter will always create a new frame for the session.
-When nil, it will use the selected frame if it does not belong to any other session."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-suggest-from-attachments t
- "When non-nil, org-noter will suggest files from the attachments
-when creating a session, if the document is missing."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-separate-notes-from-heading nil
- "When non-nil, add an empty line between each note's heading and content."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-insert-selected-text-inside-note t
- "When non-nil, it will automatically append the selected text into an existing note."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-closest-tipping-point 0.3
- "Defines when to show the closest previous note.
-
-Let x be (this value)*100. The following schematic represents the
-view (eg. a page of a PDF):
-
-+----+
-| | -> If there are notes in here, the closest previous note is not shown
-+----+--> Tipping point, at x% of the view
-| | -> When _all_ notes are in here, below the tipping point, the closest
-| | previous note will be shown.
-+----+
-
-When this value is negative, disable this feature.
-
-This setting may be overridden in a document with the function
-`org-noter-set-closest-tipping-point', which see."
- :group 'org-noter
- :type 'number)
-
-(defcustom org-noter-default-notes-file-names '("Notes.org")
- "List of possible names for the default notes file, in increasing order of priority."
- :group 'org-noter
- :type '(repeat string))
-
-(defcustom org-noter-notes-search-path '("~/Documents")
- "List of paths to check (non recursively) when searching for a notes file."
- :group 'org-noter
- :type '(repeat string))
-
-(defcustom org-noter-arrow-delay 0.2
- "Number of seconds from when the command was invoked until the tooltip arrow appears.
-
-When set to a negative number, the arrow tooltip is disabled.
-This is needed in order to keep Emacs from hanging when doing many syncs."
- :group 'org-noter
- :type 'number)
-
-(defcustom org-noter-doc-property-in-notes nil
- "If non-nil, every new note will have the document property too.
-This makes moving notes out of the root heading easier."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-insert-note-no-questions nil
- "When non-nil, `org-noter-insert-note' won't ask for a title and will always insert a new note.
-The title used will be the default one."
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-kill-frame-at-session-end t
- "If non-nil, `org-noter-kill-session' will delete the frame if others exist on the current display.'"
- :group 'org-noter
- :type 'boolean)
-
-(defcustom org-noter-insert-heading-hook nil
- "Hook being run after inserting a new heading."
- :group 'org-noter
- :type 'hook)
-
-(defface org-noter-no-notes-exist-face
- '((t
- :foreground "chocolate"
- :weight bold))
- "Face for modeline note count, when 0."
- :group 'org-noter)
-
-(defface org-noter-notes-exist-face
- '((t
- :foreground "SpringGreen"
- :weight bold))
- "Face for modeline note count, when not 0."
- :group 'org-noter)
-
-;; --------------------------------------------------------------------------------
-;; NOTE(nox): Integration with other packages
-(defcustom org-noter--check-location-property-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--parse-location-property-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--pretty-print-location-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--convert-to-location-cons-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--doc-goto-location-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--note-after-tipping-point-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--relative-position-to-view-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--get-precise-info-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-(defcustom org-noter--doc-approx-location-hook nil
- "TODO"
- :group 'org-noter
- :type 'hook)
-
-;; --------------------------------------------------------------------------------
-;; NOTE(nox): Private variables or constants
-(cl-defstruct org-noter--session
- id frame doc-buffer notes-buffer ast modified-tick doc-mode display-name notes-file-path property-text
- level num-notes-in-view window-behavior window-location doc-split-fraction auto-save-last-location
- hide-other closest-tipping-point)
-
-(defvar org-noter--sessions nil
- "List of `org-noter' sessions.")
-
-(defvar-local org-noter--session nil
- "Session associated with the current buffer.")
-
-(defvar org-noter--inhibit-location-change-handler nil
- "Prevent location change from updating point in notes.")
-
-(defvar org-noter--start-location-override nil
- "Used to open the session from the document in the right page.")
-
-(defvar-local org-noter--nov-timer nil
- "Timer for synchronizing notes after scrolling.")
-
-(defvar org-noter--arrow-location nil
- "A vector [TIMER WINDOW TOP] that shows where the arrow should appear, when idling.")
-
-(defvar org-noter--completing-read-keymap (make-sparse-keymap)
- "A `completing-read' keymap that let's the user insert spaces.")
-
-(set-keymap-parent org-noter--completing-read-keymap minibuffer-local-completion-map)
-(define-key org-noter--completing-read-keymap (kbd "SPC") 'self-insert-command)
-
-(defconst org-noter--property-behavior "NOTER_NOTES_BEHAVIOR"
- "Property for overriding global `org-noter-notes-window-behavior'.")
-
-(defconst org-noter--property-location "NOTER_NOTES_LOCATION"
- "Property for overriding global `org-noter-notes-window-location'.")
-
-(defconst org-noter--property-doc-split-fraction "NOTER_DOCUMENT_SPLIT_FRACTION"
- "Property for overriding global `org-noter-doc-split-fraction'.")
-
-(defconst org-noter--property-auto-save-last-location "NOTER_AUTO_SAVE_LAST_LOCATION"
- "Property for overriding global `org-noter-auto-save-last-location'.")
-
-(defconst org-noter--property-hide-other "NOTER_HIDE_OTHER"
- "Property for overriding global `org-noter-hide-other'.")
-
-(defconst org-noter--property-closest-tipping-point "NOTER_CLOSEST_TIPPING_POINT"
- "Property for overriding global `org-noter-closest-tipping-point'.")
-
-(defconst org-noter--note-search-no-recurse (delete 'headline (append org-element-all-elements nil))
- "List of elements that shouldn't be recursed into when searching for notes.")
-
-(defconst org-noter--id-text-property 'org-noter-session-id
- "Text property used to mark the headings with open sessions.")
-
-;; --------------------------------------------------------------------------------
-;; NOTE(nox): Utility functions
-(defun org-noter--get-new-id ()
- (catch 'break
- (while t
- (let ((id (random most-positive-fixnum)))
- (unless (cl-loop for session in org-noter--sessions
- when (= (org-noter--session-id session) id) return t)
- (throw 'break id))))))
-
-(defmacro org-noter--property-or-default (name)
- (let ((function-name (intern (concat "org-noter--" (symbol-name name) "-property")))
- (variable (intern (concat "org-noter-" (symbol-name name)))))
- `(let ((prop-value (,function-name ast)))
- (cond ((eq prop-value 'disable) nil)
- (prop-value)
- (t ,variable)))))
-
-(defun org-noter--create-session (ast document-property-value notes-file-path)
- (let* ((raw-value-not-empty (> (length (org-element-property :raw-value ast)) 0))
- (display-name (if raw-value-not-empty
- (org-element-property :raw-value ast)
- (file-name-nondirectory document-property-value)))
- (frame-name (format "Emacs Org-noter - %s" display-name))
-
- (document (find-file-noselect document-property-value))
- (document-path (expand-file-name document-property-value))
- (document-major-mode (buffer-local-value 'major-mode document))
- (document-buffer-name
- (generate-new-buffer-name (concat (unless raw-value-not-empty "Org-noter: ") display-name)))
- (document-buffer
- (if (eq document-major-mode 'nov-mode)
- document
- (make-indirect-buffer document document-buffer-name t)))
-
- (notes-buffer
- (make-indirect-buffer
- (or (buffer-base-buffer) (current-buffer))
- (generate-new-buffer-name (concat "Notes of " display-name)) t))
-
- (session
- (make-org-noter--session
- :id (org-noter--get-new-id)
- :display-name display-name
- :frame
- (if (or org-noter-always-create-frame
- (catch 'has-session
- (dolist (test-session org-noter--sessions)
- (when (eq (org-noter--session-frame test-session) (selected-frame))
- (throw 'has-session t)))))
- (make-frame `((name . ,frame-name) (fullscreen . maximized)))
- (set-frame-parameter nil 'name frame-name)
- (selected-frame))
- :doc-mode document-major-mode
- :property-text document-property-value
- :notes-file-path notes-file-path
- :doc-buffer document-buffer
- :notes-buffer notes-buffer
- :level (org-element-property :level ast)
- :window-behavior (org-noter--property-or-default notes-window-behavior)
- :window-location (org-noter--property-or-default notes-window-location)
- :doc-split-fraction (org-noter--property-or-default doc-split-fraction)
- :auto-save-last-location (org-noter--property-or-default auto-save-last-location)
- :hide-other (org-noter--property-or-default hide-other)
- :closest-tipping-point (org-noter--property-or-default closest-tipping-point)
- :modified-tick -1))
-
- (target-location org-noter--start-location-override)
- (starting-point (point)))
-
- (add-hook 'delete-frame-functions 'org-noter--handle-delete-frame)
- (push session org-noter--sessions)
-
- (with-current-buffer document-buffer
- (cond
- ;; NOTE(nox): PDF Tools
- ((eq document-major-mode 'pdf-view-mode)
- (setq buffer-file-name document-path)
- (pdf-view-mode)
- (add-hook 'pdf-view-after-change-page-hook 'org-noter--doc-location-change-handler nil t))
-
- ;; NOTE(nox): DocView
- ((eq document-major-mode 'doc-view-mode)
- (setq buffer-file-name document-path)
- (doc-view-mode)
- (advice-add 'doc-view-goto-page :after 'org-noter--location-change-advice))
-
- ;; NOTE(nox): Nov.el
- ((eq document-major-mode 'nov-mode)
- (rename-buffer document-buffer-name)
- (advice-add 'nov-render-document :after 'org-noter--nov-scroll-handler)
- (add-hook 'window-scroll-functions 'org-noter--nov-scroll-handler nil t))
-
- (t (error "This document handler is not supported :/")))
-
- (org-noter-doc-mode 1)
- (setq org-noter--session session)
- (add-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer nil t))
-
- (with-current-buffer notes-buffer
- (org-noter-notes-mode 1)
- ;; NOTE(nox): This is needed because a session created in an indirect buffer would use the point of
- ;; the base buffer (as this buffer is indirect to the base!)
- (goto-char starting-point)
- (setq buffer-file-name notes-file-path
- org-noter--session session
- fringe-indicator-alist '((truncation . nil)))
- (add-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer nil t)
- (add-hook 'window-scroll-functions 'org-noter--set-notes-scroll nil t)
- (org-noter--set-text-properties (org-noter--parse-root (vector notes-buffer document-property-value))
- (org-noter--session-id session))
- (unless target-location
- (setq target-location (org-noter--parse-location-property (org-noter--get-containing-heading t)))))
-
- (org-noter--setup-windows session)
-
- ;; NOTE(nox): This timer is for preventing reflowing too soon.
- (run-with-idle-timer
- 0.05 nil
- (lambda ()
- (with-current-buffer document-buffer
- (let ((org-noter--inhibit-location-change-handler t))
- (when target-location (org-noter--doc-goto-location target-location)))
- (org-noter--doc-location-change-handler))))))
-
-(defun org-noter--valid-session (session)
- (when session
- (if (and (frame-live-p (org-noter--session-frame session))
- (buffer-live-p (org-noter--session-doc-buffer session))
- (buffer-live-p (org-noter--session-notes-buffer session)))
- t
- (org-noter-kill-session session)
- nil)))
-
-(defmacro org-noter--with-valid-session (&rest body)
- (declare (debug (body)))
- `(let ((session org-noter--session))
- (when (org-noter--valid-session session)
- (progn ,@body))))
-
-(defun org-noter--handle-kill-buffer ()
- (org-noter--with-valid-session
- (let ((buffer (current-buffer))
- (notes-buffer (org-noter--session-notes-buffer session))
- (doc-buffer (org-noter--session-doc-buffer session)))
- ;; NOTE(nox): This needs to be checked in order to prevent session killing because of
- ;; temporary buffers with the same local variables
- (when (or (eq buffer notes-buffer)
- (eq buffer doc-buffer))
- (org-noter-kill-session session)))))
-
-(defun org-noter--handle-delete-frame (frame)
- (dolist (session org-noter--sessions)
- (when (eq (org-noter--session-frame session) frame)
- (org-noter-kill-session session))))
-
-(defun org-noter--parse-root (&optional info)
- "Parse and return the root AST.
-When used, the INFO argument may be an org-noter session or a vector [NotesBuffer PropertyText].
-If nil, the session used will be `org-noter--session'."
- (let* ((arg-is-session (org-noter--session-p info))
- (session (or (and arg-is-session info) org-noter--session))
- root-pos ast)
- (cond
- ((and (not arg-is-session) (vectorp info))
- ;; NOTE(nox): Use arguments to find heading, by trying to find the outermost parent heading with
- ;; the specified property
- (let ((notes-buffer (aref info 0))
- (wanted-prop (aref info 1)))
- (unless (and (buffer-live-p notes-buffer) (stringp wanted-prop)
- (eq (buffer-local-value 'major-mode notes-buffer) 'org-mode))
- (error "Error parsing root with invalid arguments"))
-
- (with-current-buffer notes-buffer
- (org-with-wide-buffer
- (catch 'break
- (org-back-to-heading t)
- (while t
- (when (string= (org-entry-get nil org-noter-property-doc-file) wanted-prop)
- (setq root-pos (copy-marker (point))))
- (unless (org-up-heading-safe) (throw 'break t))))))))
-
- ((org-noter--valid-session session)
- ;; NOTE(nox): Use session to find heading
- (or (and (= (buffer-chars-modified-tick (org-noter--session-notes-buffer session))
- (org-noter--session-modified-tick session))
- (setq ast (org-noter--session-ast session))) ; NOTE(nox): Cached version!
-
- ;; NOTE(nox): Find session id text property
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (let ((pos (text-property-any (point-min) (point-max) org-noter--id-text-property
- (org-noter--session-id session))))
- (when pos (setq root-pos (copy-marker pos)))))))))
-
- (unless ast
- (unless root-pos (error "Root heading not found"))
- (with-current-buffer (marker-buffer root-pos)
- (org-with-wide-buffer
- (goto-char (marker-position root-pos))
- (org-narrow-to-subtree)
- (setq ast (car (org-element-contents (org-element-parse-buffer 'greater-element))))
- (when (and (not (vectorp info)) (org-noter--valid-session session))
- (setf (org-noter--session-ast session) ast
- (org-noter--session-modified-tick session) (buffer-chars-modified-tick))))))
- ast))
-
-(defun org-noter--get-properties-end (ast &optional force-trim)
- (when ast
- (let* ((contents (org-element-contents ast))
- (section (org-element-map contents 'section 'identity nil t 'headline))
- (properties (org-element-map section 'property-drawer 'identity nil t))
- properties-end)
- (if (not properties)
- (org-element-property :contents-begin ast)
- (setq properties-end (org-element-property :end properties))
- (when (or force-trim
- (= (org-element-property :end section) properties-end))
- (while (not (eq (char-before properties-end) ?:))
- (setq properties-end (1- properties-end))))
- properties-end))))
-
-(defun org-noter--set-text-properties (ast id)
- (org-with-wide-buffer
- (when ast
- (let* ((level (org-element-property :level ast))
- (begin (org-element-property :begin ast))
- (title-begin (+ 1 level begin))
- (contents-begin (org-element-property :contents-begin ast))
- (properties-end (org-noter--get-properties-end ast t))
- (inhibit-read-only t)
- (modified (buffer-modified-p)))
- (add-text-properties (max 1 (1- begin)) begin '(read-only t))
- (add-text-properties begin (1- title-begin) `(read-only t front-sticky t ,org-noter--id-text-property ,id))
- (add-text-properties (1- title-begin) title-begin '(read-only t rear-nonsticky t))
- (add-text-properties (1- contents-begin) (1- properties-end) '(read-only t))
- (add-text-properties (1- properties-end) properties-end
- '(read-only t rear-nonsticky t))
- (set-buffer-modified-p modified)))))
-
-(defun org-noter--unset-text-properties (ast)
- (when ast
- (org-with-wide-buffer
- (let* ((begin (org-element-property :begin ast))
- (end (org-noter--get-properties-end ast t))
- (inhibit-read-only t)
- (modified (buffer-modified-p)))
- (remove-list-of-text-properties (max 1 (1- begin)) end
- `(read-only front-sticky rear-nonsticky ,org-noter--id-text-property))
- (set-buffer-modified-p modified)))))
-
-(defun org-noter--set-notes-scroll (window &rest ignored)
- (when window
- (with-selected-window window
- (org-noter--with-valid-session
- (let* ((level (org-noter--session-level session))
- (goal (* (1- level) 2))
- (current-scroll (window-hscroll)))
- (when (and (bound-and-true-p org-indent-mode) (< current-scroll goal))
- (scroll-right current-scroll)
- (scroll-left goal t)))))))
-
-(defun org-noter--insert-heading (level title &optional newlines-number location)
- "Insert a new heading at LEVEL with TITLE.
-The point will be at the start of the contents, after any
-properties, by a margin of NEWLINES-NUMBER."
- (setq newlines-number (or newlines-number 1))
- (org-insert-heading nil t)
- (let* ((initial-level (org-element-property :level (org-element-at-point)))
- (changer (if (> level initial-level) 'org-do-demote 'org-do-promote))
- (number-of-times (abs (- level initial-level))))
- (dotimes (_ number-of-times) (funcall changer))
- (insert (org-trim (replace-regexp-in-string "\n" " " title)))
-
- (org-end-of-subtree)
- (unless (bolp) (insert "\n"))
- (org-N-empty-lines-before-current (1- newlines-number))
-
- (when location
- (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location))
-
- (when org-noter-doc-property-in-notes
- (org-noter--with-valid-session
- (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
- (org-entry-put nil org-noter--property-auto-save-last-location "nil"))))
-
- (run-hooks 'org-noter-insert-heading-hook)))
-
-(defun org-noter--narrow-to-root (ast)
- (when ast
- (save-excursion
- (goto-char (org-element-property :contents-begin ast))
- (org-show-entry)
- (org-narrow-to-subtree)
- (org-cycle-hide-drawers 'all))))
-
-(defun org-noter--get-doc-window ()
- (org-noter--with-valid-session
- (or (get-buffer-window (org-noter--session-doc-buffer session)
- (org-noter--session-frame session))
- (org-noter--setup-windows org-noter--session)
- (get-buffer-window (org-noter--session-doc-buffer session)
- (org-noter--session-frame session)))))
-
-(defun org-noter--get-notes-window (&optional type)
- (org-noter--with-valid-session
- (let ((notes-buffer (org-noter--session-notes-buffer session))
- (window-location (org-noter--session-window-location session))
- (window-behavior (org-noter--session-window-behavior session))
- notes-window)
- (or (get-buffer-window notes-buffer t)
- (when (or (eq type 'force) (memq type window-behavior))
- (if (eq window-location 'other-frame)
- (let ((restore-frame (selected-frame)))
- (switch-to-buffer-other-frame notes-buffer)
- (setq notes-window (get-buffer-window notes-buffer t))
- (x-focus-frame restore-frame)
- (raise-frame (window-frame notes-window)))
-
- (with-selected-window (org-noter--get-doc-window)
- (let ((horizontal (eq window-location 'horizontal-split)))
- (setq
- notes-window
- (if (window-combined-p nil horizontal)
- ;; NOTE(nox): Reuse already existent window
- (let ((sibling-window (or (window-next-sibling) (window-prev-sibling))))
- (or (window-top-child sibling-window) (window-left-child sibling-window)
- sibling-window))
-
- (if horizontal
- (split-window-right (ceiling (* (car (org-noter--session-doc-split-fraction session))
- (window-total-width))))
- (split-window-below (ceiling (* (cdr (org-noter--session-doc-split-fraction session))
- (window-total-height)))))))))
-
- (set-window-buffer notes-window notes-buffer))
- notes-window)))))
-
-(defun org-noter--setup-windows (session)
- "Setup windows when starting session, respecting user configuration."
- (when (org-noter--valid-session session)
- (with-selected-frame (org-noter--session-frame session)
- (delete-other-windows)
- (let* ((doc-buffer (org-noter--session-doc-buffer session))
- (doc-window (selected-window))
- (notes-buffer (org-noter--session-notes-buffer session))
- notes-window)
-
- (set-window-buffer doc-window doc-buffer)
- (set-window-dedicated-p doc-window t)
-
- (with-current-buffer notes-buffer
- (org-noter--narrow-to-root (org-noter--parse-root session))
- (setq notes-window (org-noter--get-notes-window 'start))
- (org-noter--set-notes-scroll notes-window))))))
-
-(defmacro org-noter--with-selected-notes-window (error-str &rest body)
- (declare (debug ([&optional stringp] body)))
- (let ((with-error (stringp error-str)))
- `(org-noter--with-valid-session
- (let ((notes-window (org-noter--get-notes-window)))
- (if notes-window
- (with-selected-window notes-window
- ,(if with-error
- `(progn ,@body)
- (if body
- `(progn ,error-str ,@body)
- `(progn ,error-str))))
- ,(when with-error `(user-error "%s" ,error-str)))))))
-
-(defun org-noter--notes-window-behavior-property (ast)
- (let ((property (org-element-property (intern (concat ":" org-noter--property-behavior)) ast))
- value)
- (when (and (stringp property) (> (length property) 0))
- (setq value (car (read-from-string property)))
- (when (listp value) value))))
-
-(defun org-noter--notes-window-location-property (ast)
- (let ((property (org-element-property (intern (concat ":" org-noter--property-location)) ast))
- value)
- (when (and (stringp property) (> (length property) 0))
- (setq value (intern property))
- (when (memq value '(horizontal-split vertical-split other-frame)) value))))
-
-(defun org-noter--doc-split-fraction-property (ast)
- (let ((property (org-element-property (intern (concat ":" org-noter--property-doc-split-fraction)) ast))
- value)
- (when (and (stringp property) (> (length property) 0))
- (setq value (car (read-from-string property)))
- (when (consp value) value))))
-
-(defun org-noter--auto-save-last-location-property (ast)
- (let ((property (org-element-property (intern (concat ":" org-noter--property-auto-save-last-location)) ast)))
- (when (and (stringp property) (> (length property) 0))
- (if (intern property) t 'disable))))
-
-(defun org-noter--hide-other-property (ast)
- (let ((property (org-element-property (intern (concat ":" org-noter--property-hide-other)) ast)))
- (when (and (stringp property) (> (length property) 0))
- (if (intern property) t 'disable))))
-
-(defun org-noter--closest-tipping-point-property (ast)
- (let ((property (org-element-property (intern (concat ":" org-noter--property-closest-tipping-point)) ast)))
- (when (and (stringp property) (> (length property) 0))
- (ignore-errors (string-to-number property)))))
-
-(defun org-noter--doc-approx-location-cons (&optional precise-info)
- (cond
- ((memq major-mode '(doc-view-mode pdf-view-mode))
- (cons (image-mode-window-get 'page) (if (numberp precise-info) precise-info 0)))
-
- ((eq major-mode 'nov-mode)
- (cons nov-documents-index (if (integerp precise-info)
- precise-info
- (max 1 (/ (+ (window-start) (window-end nil t)) 2)))))
-
- (t (error "Unknown document type %s" major-mode))))
-
-(defun org-noter--doc-approx-location (&optional precise-info force-new-ref)
- (let ((window (if (org-noter--valid-session org-noter--session)
- (org-noter--get-doc-window)
- (selected-window))))
- (cl-assert window)
- (with-selected-window window
- (or (run-hook-with-args-until-success 'org-noter--doc-approx-location-hook major-mode
- precise-info force-new-ref)
- (org-noter--doc-approx-location-cons precise-info)))))
-
-(defun org-noter--location-change-advice (&rest _)
- (org-noter--with-valid-session (org-noter--doc-location-change-handler)))
-
-(defun org-noter--nov-scroll-handler (&rest _)
- (when org-noter--nov-timer (cancel-timer org-noter--nov-timer))
- (unless org-noter--inhibit-location-change-handler
- (setq org-noter--nov-timer (run-with-timer 0.25 nil 'org-noter--doc-location-change-handler))))
-
-(defsubst org-noter--doc-file-property (headline)
- (org-element-property (intern (concat ":" org-noter-property-doc-file)) headline))
-
-(defun org-noter--check-location-property (arg)
- (let ((property (if (stringp arg) arg
- (org-element-property (intern (concat ":" org-noter-property-note-location)) arg))))
- (when (and (stringp property) (> (length property) 0))
- (or (run-hook-with-args-until-success 'org-noter--check-location-property-hook property)
- (let ((value (car (read-from-string property))))
- (or (and (consp value) (integerp (car value)) (numberp (cdr value)))
- (integerp value)))))))
-
-(defun org-noter--parse-location-property (arg)
- (let ((property (if (stringp arg) arg
- (org-element-property (intern (concat ":" org-noter-property-note-location)) arg))))
- (when (and (stringp property) (> (length property) 0))
- (or (run-hook-with-args-until-success 'org-noter--parse-location-property-hook property)
- (let ((value (car (read-from-string property))))
- (cond ((and (consp value) (integerp (car value)) (numberp (cdr value))) value)
- ((integerp value) (cons value 0))))))))
-
-(defun org-noter--pretty-print-location (location)
- (org-noter--with-valid-session
- (or (run-hook-with-args-until-success 'org-noter--pretty-print-location-hook location)
- (format "%s" (cond
- ((memq (org-noter--session-doc-mode session) '(doc-view-mode pdf-view-mode))
- (if (or (not (cdr location)) (<= (cdr location) 0))
- (car location)
- location))
-
- ((eq (org-noter--session-doc-mode session) 'nov-mode)
- (if (or (not (cdr location)) (<= (cdr location) 1))
- (car location)
- location)))))))
-
-(defun org-noter--get-containing-heading (&optional include-root)
- "Get smallest containing heading that encloses the point and has location property.
-If the point isn't inside any heading with location property, return the outer heading.
-When INCLUDE-ROOT is non-nil, the root heading is also eligible to be returned."
- (org-noter--with-valid-session
- (org-with-wide-buffer
- (unless (org-before-first-heading-p)
- (org-back-to-heading t)
- (let (previous)
- (catch 'break
- (while t
- (let ((prop (org-noter--check-location-property (org-entry-get nil org-noter-property-note-location)))
- (at-root (equal (org-noter--session-id session)
- (get-text-property (point) org-noter--id-text-property)))
- (heading (org-element-at-point)))
- (when (and prop (or include-root (not at-root)))
- (throw 'break heading))
-
- (when (or at-root (not (org-up-heading-safe)))
- (throw 'break (if include-root heading previous)))
-
- (setq previous heading)))))))))
-
-(defun org-noter--doc-get-page-slice ()
- "Return (slice-top . slice-height)."
- (let* ((slice (or (image-mode-window-get 'slice) '(0 0 1 1)))
- (slice-top (float (nth 1 slice)))
- (slice-height (float (nth 3 slice))))
- (when (or (> slice-top 1)
- (> slice-height 1))
- (let ((height (cdr (image-size (image-mode-window-get 'image) t))))
- (setq slice-top (/ slice-top height)
- slice-height (/ slice-height height))))
- (cons slice-top slice-height)))
-
-(defun org-noter--conv-page-scroll-percentage (scroll)
- (let* ((slice (org-noter--doc-get-page-slice))
- (display-height (cdr (image-display-size (image-get-display-property))))
- (display-percentage (/ scroll display-height))
- (percentage (+ (car slice) (* (cdr slice) display-percentage))))
- (max 0 (min 1 percentage))))
-
-(defun org-noter--conv-page-percentage-scroll (percentage)
- (let* ((slice (org-noter--doc-get-page-slice))
- (display-height (cdr (image-display-size (image-get-display-property))))
- (display-percentage (min 1 (max 0 (/ (- percentage (car slice)) (cdr slice)))))
- (scroll (max 0 (floor (* display-percentage display-height)))))
- scroll))
-
-(defun org-noter--get-precise-info ()
- (org-noter--with-valid-session
- (let ((window (org-noter--get-doc-window))
- (mode (org-noter--session-doc-mode session))
- event)
- (with-selected-window window
- (cond
- ((run-hook-with-args-until-success 'org-noter--get-precise-info-hook mode))
-
- ((eq mode 'pdf-view-mode)
- (if (pdf-view-active-region-p)
- (cadar (pdf-view-active-region))
- (while (not (and (eq 'mouse-1 (car event))
- (eq window (posn-window (event-start event)))))
- (setq event (read-event "Click where you want the start of the note to be!")))
- (org-noter--conv-page-scroll-percentage (+ (window-vscroll)
- (cdr (posn-col-row (event-start event)))))))
-
- ((eq mode 'doc-view-mode)
- (while (not (and (eq 'mouse-1 (car event))
- (eq window (posn-window (event-start event)))))
- (setq event (read-event "Click where you want the start of the note to be!")))
- (org-noter--conv-page-scroll-percentage (+ (window-vscroll)
- (cdr (posn-col-row (event-start event))))))
-
- ((eq mode 'nov-mode)
- (if (region-active-p)
- (min (mark) (point))
- (while (not (and (eq 'mouse-1 (car event))
- (eq window (posn-window (event-start event)))))
- (setq event (read-event "Click where you want the start of the note to be!")))
- (posn-point (event-start event)))))))))
-
-(defun org-noter--show-arrow ()
- (when (and org-noter--arrow-location
- (window-live-p (aref org-noter--arrow-location 1)))
- (with-selected-window (aref org-noter--arrow-location 1)
- (pdf-util-tooltip-arrow (aref org-noter--arrow-location 2))))
- (setq org-noter--arrow-location nil))
-
-(defun org-noter--doc-goto-location (location)
- "Go to location specified by LOCATION."
- (org-noter--with-valid-session
- (let ((window (org-noter--get-doc-window))
- (mode (org-noter--session-doc-mode session)))
- (with-selected-window window
- (cond
- ((run-hook-with-args-until-success 'org-noter--doc-goto-location-hook mode location))
-
- ((memq mode '(doc-view-mode pdf-view-mode))
- (if (eq mode 'doc-view-mode)
- (doc-view-goto-page (car location))
- (pdf-view-goto-page (car location))
- ;; NOTE(nox): This timer is needed because the tooltip may introduce a delay,
- ;; so syncing multiple pages was slow
- (when (>= org-noter-arrow-delay 0)
- (when org-noter--arrow-location (cancel-timer (aref org-noter--arrow-location 0)))
- (setq org-noter--arrow-location
- (vector (run-with-idle-timer org-noter-arrow-delay nil 'org-noter--show-arrow)
- window
- (cdr location)))))
- (image-scroll-up (- (org-noter--conv-page-percentage-scroll (cdr location))
- (window-vscroll))))
-
- ((eq mode 'nov-mode)
- (setq nov-documents-index (car location))
- (nov-render-document)
- (goto-char (cdr location))
- (recenter)))
- ;; NOTE(nox): This needs to be here, because it would be issued anyway after
- ;; everything and would run org-noter--nov-scroll-handler.
- (redisplay)))))
-
-(defun org-noter--compare-location-cons (comp l1 l2)
- "Compare L1 and L2, which are location cons.
-See `org-noter--compare-locations'"
- (cl-assert (and (consp l1) (consp l2)))
- (cond ((eq comp '=)
- (and (= (car l1) (car l2))
- (= (cdr l1) (cdr l2))))
- ((eq comp '<)
- (or (< (car l1) (car l2))
- (and (= (car l1) (car l2))
- (< (cdr l1) (cdr l2)))))
- ((eq comp '<=)
- (or (< (car l1) (car l2))
- (and (= (car l1) (car l2))
- (<= (cdr l1) (cdr l2)))))
- ((eq comp '>)
- (or (> (car l1) (car l2))
- (and (= (car l1) (car l2))
- (> (cdr l1) (cdr l2)))))
- ((eq comp '>=)
- (or (> (car l1) (car l2))
- (and (= (car l1) (car l2))
- (>= (cdr l1) (cdr l2)))))
- ((eq comp '>f)
- (or (> (car l1) (car l2))
- (and (= (car l1) (car l2))
- (< (cdr l1) (cdr l2)))))
- (t (error "Comparison operator %s not known" comp))))
-
-(defun org-noter--compare-locations (comp l1 l2)
- "Compare L1 and L2.
-When COMP is '<, '<=, '>, or '>=, it works as expected.
-When COMP is '>f, it will return t when L1 is a page greater than
-L2 or, when in the same page, if L1 is the _f_irst of the two."
- (cond ((not l1) nil)
- ((not l2) t)
- (t
- (setq l1 (or (run-hook-with-args-until-success 'org-noter--convert-to-location-cons-hook l1) l1)
- l2 (or (run-hook-with-args-until-success 'org-noter--convert-to-location-cons-hook l2) l2))
- (org-noter--compare-location-cons comp l1 l2))))
-
-(defun org-noter--show-note-entry (session note)
- "This will show the note entry and its children.
-Every direct subheading _until_ the first heading that doesn't
-belong to the same view (ie. until a heading with location or
-document property) will be opened."
- (save-excursion
- (goto-char (org-element-property :contents-begin note))
- (org-show-set-visibility t)
- (org-element-map (org-element-contents note) 'headline
- (lambda (headline)
- (let ((doc-file (org-noter--doc-file-property headline)))
- (if (or (and doc-file (not (string= doc-file (org-noter--session-property-text session))))
- (org-noter--check-location-property headline))
- t
- (goto-char (org-element-property :begin headline))
- (org-show-entry)
- (org-show-children)
- nil)))
- nil t org-element-all-elements)))
-
-(defun org-noter--focus-notes-region (view-info)
- (org-noter--with-selected-notes-window
- (if (org-noter--session-hide-other session)
- (save-excursion
- (goto-char (org-element-property :begin (org-noter--parse-root)))
- (outline-hide-subtree))
- (org-cycle-hide-drawers 'all))
-
- (let* ((notes-cons (org-noter--view-info-notes view-info))
- (regions (or (org-noter--view-info-regions view-info)
- (org-noter--view-info-prev-regions view-info)))
- (point-before (point))
- target-region
- point-inside-target-region)
- (cond
- (notes-cons
- (dolist (note-cons notes-cons) (org-noter--show-note-entry session (car note-cons)))
-
- (setq target-region (or (catch 'result (dolist (region regions)
- (when (and (>= point-before (car region))
- (or (save-restriction (goto-char (cdr region)) (eobp))
- (< point-before (cdr region))))
- (setq point-inside-target-region t)
- (throw 'result region))))
- (car regions)))
-
- (let ((begin (car target-region)) (end (cdr target-region)) num-lines
- (target-char (if point-inside-target-region
- point-before
- (org-noter--get-properties-end (caar notes-cons))))
- (window-start (window-start)) (window-end (window-end nil t)))
- (setq num-lines (count-screen-lines begin end))
-
- (cond
- ((> num-lines (window-height))
- (goto-char begin)
- (recenter 0))
-
- ((< begin window-start)
- (goto-char begin)
- (recenter 0))
-
- ((> end window-end)
- (goto-char end)
- (recenter -2)))
-
- (goto-char target-char)))
-
- (t (org-noter--show-note-entry session (org-noter--parse-root)))))
-
- (org-cycle-show-empty-lines t)))
-
-(defun org-noter--get-current-view ()
- "Return a vector with the current view information."
- (org-noter--with-valid-session
- (let ((mode (org-noter--session-doc-mode session)))
- (with-selected-window (org-noter--get-doc-window)
- (cond ((memq mode '(doc-view-mode pdf-view-mode))
- (vector 'paged (car (org-noter--doc-approx-location-cons))))
- ((eq mode 'nov-mode)
- (vector 'nov
- (org-noter--doc-approx-location-cons (window-start))
- (org-noter--doc-approx-location-cons (window-end nil t))))
- (t (error "Unknown document type")))))))
-
-(defun org-noter--note-after-tipping-point (point location view)
- ;; NOTE(nox): This __assumes__ the note is inside the view!
- (let (hook-result)
- (cond
- ((setq hook-result (run-hook-with-args-until-success 'org-noter--note-after-tipping-point-hook
- point location view))
- (cdr hook-result))
- ((eq (aref view 0) 'paged)
- (> (cdr location) point))
- ((eq (aref view 0) 'nov)
- (> (cdr location) (+ (* point (- (cdr (aref view 2)) (cdr (aref view 1))))
- (cdr (aref view 1))))))))
-
-(defun org-noter--relative-position-to-view (location view)
- (cond
- ((run-hook-with-args-until-success 'org-noter--relative-position-to-view-hook location view))
-
- ((eq (aref view 0) 'paged)
- (let ((note-page (car location))
- (view-page (aref view 1)))
- (cond ((< note-page view-page) 'before)
- ((= note-page view-page) 'inside)
- (t 'after))))
-
- ((eq (aref view 0) 'nov)
- (let ((view-top (aref view 1))
- (view-bot (aref view 2)))
- (cond ((org-noter--compare-locations '< location view-top) 'before)
- ((org-noter--compare-locations '<= location view-bot) 'inside)
- (t 'after))))))
-
-(defmacro org-noter--view-region-finish (info &optional terminating-headline)
- `(when ,info
- ,(if terminating-headline
- `(push (cons (aref ,info 1) (min (aref ,info 2) (org-element-property :begin ,terminating-headline)))
- (gv-deref (aref ,info 0)))
- `(push (cons (aref ,info 1) (aref ,info 2)) (gv-deref (aref ,info 0))))
- (setq ,info nil)))
-
-(defmacro org-noter--view-region-add (info list-name headline)
- `(progn
- (when (and ,info (not (eq (aref ,info 3) ',list-name)))
- (org-noter--view-region-finish ,info ,headline))
-
- (if ,info
- (setf (aref ,info 2) (max (aref ,info 2) (org-element-property :end ,headline)))
- (setq ,info (vector (gv-ref ,list-name)
- (org-element-property :begin ,headline) (org-element-property :end ,headline)
- ',list-name)))))
-
-;; NOTE(nox): notes is a list of (HEADING . HEADING-TO-INSERT-TEXT-BEFORE):
-;; - HEADING is the root heading of the note
-;; - SHOULD-ADD-SPACE indicates if there should be extra spacing when inserting text to the note (ie. the
-;; note has contents)
-(cl-defstruct org-noter--view-info notes regions prev-regions reference-for-insertion)
-
-(defun org-noter--get-view-info (view &optional new-location)
- "Return VIEW related information.
-
-When optional NEW-LOCATION is provided, it will be used to find
-the best heading to serve as a reference to create the new one
-relative to."
- (when view
- (org-noter--with-valid-session
- (let ((contents (org-element-contents (org-noter--parse-root)))
- (preamble t)
- notes-in-view regions-in-view
- reference-for-insertion reference-location
- (all-after-tipping-point t)
- (closest-tipping-point (and (>= (org-noter--session-closest-tipping-point session) 0)
- (org-noter--session-closest-tipping-point session)))
- closest-notes closest-notes-regions closest-notes-location
- ignore-until-level
- current-region-info) ;; NOTE(nox): [REGIONS-LIST-PTR START MAX-END REGIONS-LIST-NAME]
-
- (org-element-map contents 'headline
- (lambda (headline)
- (let ((doc-file (org-noter--doc-file-property headline))
- (location (org-noter--parse-location-property headline)))
- (when (and ignore-until-level (<= (org-element-property :level headline) ignore-until-level))
- (setq ignore-until-level nil))
-
- (cond
- (ignore-until-level) ;; NOTE(nox): This heading is ignored, do nothing
-
- ((and doc-file (not (string= doc-file (org-noter--session-property-text session))))
- (org-noter--view-region-finish current-region-info headline)
- (setq ignore-until-level (org-element-property :level headline))
- (when (and preamble new-location
- (or (not reference-for-insertion)
- (>= (org-element-property :begin headline)
- (org-element-property :end (cdr reference-for-insertion)))))
- (setq reference-for-insertion (cons 'after headline))))
-
- (location
- (let ((relative-position (org-noter--relative-position-to-view location view)))
- (cond
- ((eq relative-position 'inside)
- (push (cons headline nil) notes-in-view)
-
- (org-noter--view-region-add current-region-info regions-in-view headline)
-
- (setq all-after-tipping-point
- (and all-after-tipping-point (org-noter--note-after-tipping-point
- closest-tipping-point location view))))
-
- (t
- (when current-region-info
- (let ((note-cons-to-change (cond ((eq (aref current-region-info 3) 'regions-in-view)
- (car notes-in-view))
- ((eq (aref current-region-info 3) 'closest-notes-regions)
- (car closest-notes)))))
- (when (< (org-element-property :begin headline)
- (org-element-property :end (car note-cons-to-change)))
- (setcdr note-cons-to-change headline))))
-
- (let ((eligible-for-before (and closest-tipping-point all-after-tipping-point
- (eq relative-position 'before))))
- (cond ((and eligible-for-before
- (org-noter--compare-locations '> location closest-notes-location))
- (setq closest-notes (list (cons headline nil))
- closest-notes-location location
- current-region-info nil
- closest-notes-regions nil)
- (org-noter--view-region-add current-region-info closest-notes-regions headline))
-
- ((and eligible-for-before (equal location closest-notes-location))
- (push (cons headline nil) closest-notes)
- (org-noter--view-region-add current-region-info closest-notes-regions headline))
-
- (t (org-noter--view-region-finish current-region-info headline)))))))
-
- (when new-location
- (setq preamble nil)
- (cond ((and (org-noter--compare-locations '<= location new-location)
- (or (eq (car reference-for-insertion) 'before)
- (org-noter--compare-locations '>= location reference-location)))
- (setq reference-for-insertion (cons 'after headline)
- reference-location location))
-
- ((and (eq (car reference-for-insertion) 'after)
- (< (org-element-property :begin headline)
- (org-element-property :end (cdr reference-for-insertion)))
- (org-noter--compare-locations '>= location new-location))
- (setq reference-for-insertion (cons 'before headline)
- reference-location location)))))
-
- (t
- (when (and preamble new-location
- (or (not reference-for-insertion)
- (>= (org-element-property :begin headline)
- (org-element-property :end (cdr reference-for-insertion)))))
- (setq reference-for-insertion (cons 'after headline)))))))
- nil nil org-noter--note-search-no-recurse)
-
- (org-noter--view-region-finish current-region-info)
-
- (setf (org-noter--session-num-notes-in-view session) (length notes-in-view))
-
- (when all-after-tipping-point (setq notes-in-view (append closest-notes notes-in-view)))
-
- (make-org-noter--view-info
- :notes (nreverse notes-in-view)
- :regions (nreverse regions-in-view)
- :prev-regions (nreverse closest-notes-regions)
- :reference-for-insertion reference-for-insertion)))))
-
-(defun org-noter--make-view-info-for-single-note (session headline)
- (let ((not-belonging-element
- (org-element-map (org-element-contents headline) 'headline
- (lambda (headline)
- (let ((doc-file (org-noter--doc-file-property headline)))
- (and (or (and doc-file (not (string= doc-file (org-noter--session-property-text session))))
- (org-noter--check-location-property headline))
- headline)))
- nil t)))
-
- (make-org-noter--view-info
- ;; NOTE(nox): The cdr is only used when inserting, doesn't matter here
- :notes (list (cons headline nil))
- :regions (list (cons (org-element-property :begin headline)
- (or (and not-belonging-element (org-element-property :begin not-belonging-element))
- (org-element-property :end headline)))))))
-
-(defun org-noter--doc-location-change-handler ()
- (org-noter--with-valid-session
- (let ((view-info (org-noter--get-view-info (org-noter--get-current-view))))
- (force-mode-line-update t)
- (unless org-noter--inhibit-location-change-handler
- (org-noter--get-notes-window (cond ((org-noter--view-info-regions view-info) 'scroll)
- ((org-noter--view-info-prev-regions view-info) 'only-prev)))
- (org-noter--focus-notes-region view-info)))
-
- (when (org-noter--session-auto-save-last-location session) (org-noter-set-start-location))))
-
-(defun org-noter--mode-line-text ()
- (org-noter--with-valid-session
- (let* ((number-of-notes (or (org-noter--session-num-notes-in-view session) 0)))
- (cond ((= number-of-notes 0) (propertize " 0 notes " 'face 'org-noter-no-notes-exist-face))
- ((= number-of-notes 1) (propertize " 1 note " 'face 'org-noter-notes-exist-face))
- (t (propertize (format " %d notes " number-of-notes) 'face 'org-noter-notes-exist-face))))))
-
-;; NOTE(nox): From machc/pdf-tools-org
-(defun org-noter--pdf-tools-edges-to-region (edges)
- "Get 4-entry region (LEFT TOP RIGHT BOTTOM) from several EDGES."
- (when edges
- (let ((left0 (nth 0 (car edges)))
- (top0 (nth 1 (car edges)))
- (bottom0 (nth 3 (car edges)))
- (top1 (nth 1 (car (last edges))))
- (right1 (nth 2 (car (last edges))))
- (bottom1 (nth 3 (car (last edges)))))
- (list left0
- (+ top0 (/ (- bottom0 top0) 3))
- right1
- (- bottom1 (/ (- bottom1 top1) 3))))))
-
-(defun org-noter--check-if-document-is-annotated-on-file (document-path notes-path)
- ;; NOTE(nox): In order to insert the correct file contents
- (let ((buffer (find-buffer-visiting notes-path)))
- (when buffer (with-current-buffer buffer (save-buffer)))
-
- (with-temp-buffer
- (insert-file-contents notes-path)
- (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 notes-path))
- document-path)
- ;; NOTE(nox): This notes file has the document we want!
- (throw 'break t)))))))
-
-(defsubst org-noter--check-doc-prop (doc-prop)
- (and doc-prop (not (file-directory-p doc-prop)) (file-readable-p doc-prop)))
-
-(defun org-noter--get-or-read-document-property (inherit-prop &optional force-new)
- (let ((doc-prop (and (not force-new) (org-entry-get nil org-noter-property-doc-file inherit-prop))))
- (unless (org-noter--check-doc-prop doc-prop)
- (setq doc-prop nil)
-
- (when org-noter-suggest-from-attachments
- (require 'org-attach)
- (let* ((attach-dir (org-attach-dir))
- (attach-list (and attach-dir (org-attach-file-list attach-dir))))
- (when (and attach-list (y-or-n-p "Do you want to annotate an attached file?"))
- (setq doc-prop (completing-read "File to annotate: " attach-list nil t))
- (when doc-prop (setq doc-prop (file-relative-name (expand-file-name doc-prop attach-dir)))))))
-
- (unless (org-noter--check-doc-prop doc-prop)
- (setq doc-prop (expand-file-name
- (read-file-name
- "Invalid or no document property found. Please specify a document path: " nil nil t)))
- (when (or (file-directory-p doc-prop) (not (file-readable-p doc-prop))) (user-error "Invalid file path"))
- (when (y-or-n-p "Do you want a relative file name? ") (setq doc-prop (file-relative-name doc-prop))))
-
- (org-entry-put nil org-noter-property-doc-file doc-prop))
- doc-prop))
-
-(defun org-noter--other-frames (&optional this-frame)
- "Returns non-`nil' when there is at least another frame"
- (setq this-frame (or this-frame (selected-frame)))
- (catch 'other-frame
- (dolist (frame (visible-frame-list))
- (unless (or (eq this-frame frame)
- (frame-parent frame)
- (frame-parameter frame 'delete-before))
- (throw 'other-frame frame)))))
-
-;; --------------------------------------------------------------------------------
-;; NOTE(nox): User commands
-(defun org-noter-set-start-location (&optional arg)
- "When opening a session with this document, go to the current location.
-With a prefix ARG, remove start location."
- (interactive "P")
- (org-noter--with-valid-session
- (let ((inhibit-read-only t)
- (ast (org-noter--parse-root))
- (location (org-noter--doc-approx-location 'interactive)))
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if arg
- (org-entry-delete nil org-noter-property-note-location)
- (org-entry-put nil org-noter-property-note-location
- (org-noter--pretty-print-location location))))))))
-
-(defun org-noter-set-auto-save-last-location (arg)
- "This toggles saving the last visited location for this document.
-With a prefix ARG, delete the current setting and use the default."
- (interactive "P")
- (org-noter--with-valid-session
- (let ((inhibit-read-only t)
- (ast (org-noter--parse-root))
- (new-setting (if arg
- org-noter-auto-save-last-location
- (not (org-noter--session-auto-save-last-location session)))))
- (setf (org-noter--session-auto-save-last-location session)
- new-setting)
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if arg
- (org-entry-delete nil org-noter--property-auto-save-last-location)
- (org-entry-put nil org-noter--property-auto-save-last-location (format "%s" new-setting)))
- (unless new-setting (org-entry-delete nil org-noter-property-note-location)))))))
-
-(defun org-noter-set-hide-other (arg)
- "This toggles hiding other headings for the current session.
-- With a prefix \\[universal-argument], set the current setting permanently for this document.
-- With a prefix \\[universal-argument] \\[universal-argument], remove the setting and use the default."
- (interactive "P")
- (org-noter--with-valid-session
- (let* ((inhibit-read-only t)
- (ast (org-noter--parse-root))
- (persistent
- (cond ((equal arg '(4)) 'write)
- ((equal arg '(16)) 'remove)))
- (new-setting
- (cond ((eq persistent 'write) (org-noter--session-hide-other session))
- ((eq persistent 'remove) org-noter-hide-other)
- ('other-cases (not (org-noter--session-hide-other session))))))
- (setf (org-noter--session-hide-other session) new-setting)
- (when persistent
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if (eq persistent 'write)
- (org-entry-put nil org-noter--property-hide-other (format "%s" new-setting))
- (org-entry-delete nil org-noter--property-hide-other))))))))
-
-(defun org-noter-set-closest-tipping-point (arg)
- "This sets the closest note tipping point (see `org-noter-closest-tipping-point')
-- With a prefix \\[universal-argument], set it permanently for this document.
-- With a prefix \\[universal-argument] \\[universal-argument], remove the setting and use the default."
- (interactive "P")
- (org-noter--with-valid-session
- (let* ((ast (org-noter--parse-root))
- (inhibit-read-only t)
- (persistent (cond ((equal arg '(4)) 'write)
- ((equal arg '(16)) 'remove)))
- (new-setting (if (eq persistent 'remove)
- org-noter-closest-tipping-point
- (read-number "New tipping point: " (org-noter--session-closest-tipping-point session)))))
- (setf (org-noter--session-closest-tipping-point session) new-setting)
- (when persistent
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if (eq persistent 'write)
- (org-entry-put nil org-noter--property-closest-tipping-point (format "%f" new-setting))
- (org-entry-delete nil org-noter--property-closest-tipping-point))))))))
-
-(defun org-noter-set-notes-window-behavior (arg)
- "Set the notes window behaviour for the current session.
-With a prefix ARG, it becomes persistent for that document.
-
-See `org-noter-notes-window-behavior' for more information."
- (interactive "P")
- (org-noter--with-valid-session
- (let* ((inhibit-read-only t)
- (ast (org-noter--parse-root))
- (possible-behaviors (list '("Default" . default)
- '("On start" . start)
- '("On scroll" . scroll)
- '("On scroll to location that only has previous notes" . only-prev)
- '("Never" . never)))
- chosen-behaviors)
-
- (while (> (length possible-behaviors) 1)
- (let ((chosen-pair (assoc (completing-read "Behavior: " possible-behaviors nil t) possible-behaviors)))
- (cond ((eq (cdr chosen-pair) 'default) (setq possible-behaviors nil))
-
- ((eq (cdr chosen-pair) 'never) (setq chosen-behaviors (list 'never)
- possible-behaviors nil))
-
- ((eq (cdr chosen-pair) 'done) (setq possible-behaviors nil))
-
- (t (push (cdr chosen-pair) chosen-behaviors)
- (setq possible-behaviors (delq chosen-pair possible-behaviors))
- (when (= (length chosen-behaviors) 1)
- (setq possible-behaviors (delq (rassq 'default possible-behaviors) possible-behaviors)
- possible-behaviors (delq (rassq 'never possible-behaviors) possible-behaviors))
- (push (cons "Done" 'done) possible-behaviors))))))
-
- (setf (org-noter--session-window-behavior session)
- (or chosen-behaviors org-noter-notes-window-behavior))
-
- (when arg
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if chosen-behaviors
- (org-entry-put nil org-noter--property-behavior (format "%s" chosen-behaviors))
- (org-entry-delete nil org-noter--property-behavior))))))))
-
-(defun org-noter-set-notes-window-location (arg)
- "Set the notes window default location for the current session.
-With a prefix ARG, it becomes persistent for that document.
-
-See `org-noter-notes-window-behavior' for more information."
- (interactive "P")
- (org-noter--with-valid-session
- (let* ((inhibit-read-only t)
- (ast (org-noter--parse-root))
- (location-possibilities
- '(("Default" . nil)
- ("Horizontal split" . horizontal-split)
- ("Vertical split" . vertical-split)
- ("Other frame" . other-frame)))
- (location
- (cdr (assoc (completing-read "Location: " location-possibilities nil t)
- location-possibilities)))
- (notes-buffer (org-noter--session-notes-buffer session)))
-
- (setf (org-noter--session-window-location session)
- (or location org-noter-notes-window-location))
-
- (let (exists)
- (dolist (window (get-buffer-window-list notes-buffer nil t))
- (setq exists t)
- (with-selected-frame (window-frame window)
- (if (= (count-windows) 1)
- (delete-frame)
- (delete-window window))))
- (when exists (org-noter--get-notes-window 'force)))
-
- (when arg
- (with-current-buffer notes-buffer
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if location
- (org-entry-put nil org-noter--property-location
- (format "%s" location))
- (org-entry-delete nil org-noter--property-location))))))))
-
-(defun org-noter-set-doc-split-fraction (arg)
- "Set the fraction of the frame that the document window will occupy when split.
-- With a prefix \\[universal-argument], set it permanently for this document.
-- With a prefix \\[universal-argument] \\[universal-argument], remove the setting and use the default."
- (interactive "P")
- (org-noter--with-valid-session
- (let* ((ast (org-noter--parse-root))
- (inhibit-read-only t)
- (persistent (cond ((equal arg '(4)) 'write)
- ((equal arg '(16)) 'remove)))
- (current-setting (org-noter--session-doc-split-fraction session))
- (new-setting
- (if (eq persistent 'remove)
- org-noter-doc-split-fraction
- (cons (read-number "Horizontal fraction: " (car current-setting))
- (read-number "Vertical fraction: " (cdr current-setting))))))
- (setf (org-noter--session-doc-split-fraction session) new-setting)
- (when (org-noter--get-notes-window)
- (with-current-buffer (org-noter--session-doc-buffer session)
- (delete-other-windows)
- (org-noter--get-notes-window 'force)))
-
- (when persistent
- (with-current-buffer (org-noter--session-notes-buffer session)
- (org-with-wide-buffer
- (goto-char (org-element-property :begin ast))
- (if (eq persistent 'write)
- (org-entry-put nil org-noter--property-doc-split-fraction (format "%s" new-setting))
- (org-entry-delete nil org-noter--property-doc-split-fraction))))))))
-
-(defun org-noter-kill-session (&optional session)
- "Kill an `org-noter' session.
-
-When called interactively, if there is no prefix argument and the
-buffer has an annotation session, it will kill it; else, it will
-show a list of open `org-noter' sessions, asking for which to
-kill.
-
-When called from elisp code, you have to pass in the SESSION you
-want to kill."
- (interactive "P")
- (when (and (called-interactively-p 'any) (> (length org-noter--sessions) 0))
- ;; NOTE(nox): `session' is representing a prefix argument
- (if (and org-noter--session (not session))
- (setq session org-noter--session)
- (setq session nil)
- (let (collection default doc-display-name notes-file-name display)
- (dolist (session org-noter--sessions)
- (setq doc-display-name (org-noter--session-display-name session)
- notes-file-name (file-name-nondirectory
- (org-noter--session-notes-file-path session))
- display (concat doc-display-name " - " notes-file-name))
- (when (eq session org-noter--session) (setq default display))
- (push (cons display session) collection))
- (setq session (cdr (assoc (completing-read "Which session? " collection nil t
- nil nil default)
- collection))))))
-
- (when (and session (memq session org-noter--sessions))
- (setq org-noter--sessions (delq session org-noter--sessions))
-
- (when (eq (length org-noter--sessions) 0)
- (remove-hook 'delete-frame-functions 'org-noter--handle-delete-frame)
- (advice-remove 'doc-view-goto-page 'org-noter--location-change-advice)
- (advice-remove 'nov-render-document 'org-noter--nov-scroll-handler))
-
- (let* ((ast (org-noter--parse-root session))
- (frame (org-noter--session-frame session))
- (notes-buffer (org-noter--session-notes-buffer session))
- (base-buffer (buffer-base-buffer notes-buffer))
- (notes-modified (buffer-modified-p base-buffer))
- (doc-buffer (org-noter--session-doc-buffer session)))
-
- (dolist (window (get-buffer-window-list notes-buffer nil t))
- (with-selected-frame (window-frame window)
- (if (= (count-windows) 1)
- (when (org-noter--other-frames) (delete-frame))
- (delete-window window))))
-
- (with-current-buffer notes-buffer
- (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t)
- (restore-buffer-modified-p nil))
- (kill-buffer notes-buffer)
-
- (with-current-buffer base-buffer
- (org-noter--unset-text-properties ast)
- (set-buffer-modified-p notes-modified))
-
- (with-current-buffer doc-buffer
- (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t))
- (kill-buffer doc-buffer)
-
- (when (frame-live-p frame)
- (if (and (org-noter--other-frames) org-noter-kill-frame-at-session-end)
- (delete-frame frame)
- (progn
- (delete-other-windows)
- (set-frame-parameter nil 'name nil)))))))
-
-(defun org-noter-create-skeleton ()
- "Create notes skeleton with the PDF outline or annotations.
-Only available with PDF Tools."
- (interactive)
- (org-noter--with-valid-session
- (cond
- ((eq (org-noter--session-doc-mode session) 'pdf-view-mode)
- (let* ((ast (org-noter--parse-root))
- (top-level (org-element-property :level ast))
- (options '(("Outline" . (outline))
- ("Annotations" . (annots))
- ("Both" . (outline annots))))
- answer output-data)
- (with-current-buffer (org-noter--session-doc-buffer session)
- (setq answer (assoc (completing-read "What do you want to import? " options nil t) options))
-
- (when (memq 'outline answer)
- (dolist (item (pdf-info-outline))
- (let ((type (alist-get 'type item))
- (page (alist-get 'page item))
- (depth (alist-get 'depth item))
- (title (alist-get 'title item))
- (top (alist-get 'top item)))
- (when (and (eq type 'goto-dest) (> page 0))
- (push (vector title (cons page top) (1+ depth) nil) output-data)))))
-
- (when (memq 'annots answer)
- (let ((possible-annots (list '("Highlights" . highlight)
- '("Underlines" . underline)
- '("Squigglies" . squiggly)
- '("Text notes" . text)
- '("Strikeouts" . strike-out)
- '("Links" . link)
- '("ALL" . all)))
- chosen-annots insert-contents pages-with-links)
- (while (> (length possible-annots) 1)
- (let* ((chosen-string (completing-read "Which types of annotations do you want? "
- possible-annots nil t))
- (chosen-pair (assoc chosen-string possible-annots)))
- (cond ((eq (cdr chosen-pair) 'all)
- (dolist (annot possible-annots)
- (when (and (cdr annot) (not (eq (cdr annot) 'all)))
- (push (cdr annot) chosen-annots)))
- (setq possible-annots nil))
- ((cdr chosen-pair)
- (push (cdr chosen-pair) chosen-annots)
- (setq possible-annots (delq chosen-pair possible-annots))
- (when (= 1 (length chosen-annots)) (push '("DONE") possible-annots)))
- (t
- (setq possible-annots nil)))))
-
- (setq insert-contents (y-or-n-p "Should we insert the annotations contents? "))
-
- (dolist (item (pdf-info-getannots))
- (let* ((type (alist-get 'type item))
- (page (alist-get 'page item))
- (edges (or (org-noter--pdf-tools-edges-to-region (alist-get 'markup-edges item))
- (alist-get 'edges item)))
- (top (nth 1 edges))
- (item-subject (alist-get 'subject item))
- (item-contents (alist-get 'contents item))
- name contents)
- (when (and (memq type chosen-annots) (> page 0))
- (if (eq type 'link)
- (cl-pushnew page pages-with-links)
- (setq name (cond ((eq type 'highlight) "Highlight")
- ((eq type 'underline) "Underline")
- ((eq type 'squiggly) "Squiggly")
- ((eq type 'text) "Text note")
- ((eq type 'strike-out) "Strikeout")))
-
- (when insert-contents
- (setq contents (cons (pdf-info-gettext page edges)
- (and (or (and item-subject (> (length item-subject) 0))
- (and item-contents (> (length item-contents) 0)))
- (concat (or item-subject "")
- (if (and item-subject item-contents) "\n" "")
- (or item-contents ""))))))
-
- (push (vector (format "%s on page %d" name page) (cons page top) 'inside contents)
- output-data)))))
-
- (dolist (page pages-with-links)
- (let ((links (pdf-info-pagelinks page))
- type)
- (dolist (link links)
- (setq type (alist-get 'type link))
- (unless (eq type 'goto-dest) ;; NOTE(nox): Ignore internal links
- (let* ((edges (alist-get 'edges link))
- (title (alist-get 'title link))
- (top (nth 1 edges))
- (target-page (alist-get 'page link))
- target heading-text)
-
- (unless (and title (> (length title) 0)) (setq title (pdf-info-gettext page edges)))
-
- (cond
- ((eq type 'uri)
- (setq target (alist-get 'uri link)
- heading-text (format "Link on page %d: [[%s][%s]]" page target title)))
-
- ((eq type 'goto-remote)
- (setq target (concat "file:" (alist-get 'filename link))
- heading-text (format "Link to document on page %d: [[%s][%s]]" page target title))
- (when target-page
- (setq heading-text (concat heading-text (format " (target page: %d)" target-page)))))
-
- (t (error "Unexpected link type")))
-
- (push (vector heading-text (cons page top) 'inside nil) output-data))))))))
-
-
- (when output-data
- (if (memq 'annots answer)
- (setq output-data
- (sort output-data
- (lambda (e1 e2)
- (or (not (aref e1 1))
- (and (aref e2 1)
- (org-noter--compare-locations '< (aref e1 1) (aref e2 1)))))))
- (setq output-data (nreverse output-data)))
-
- (push (vector "Skeleton" nil 1 nil) output-data)))
-
- (with-current-buffer (org-noter--session-notes-buffer session)
- ;; NOTE(nox): org-with-wide-buffer can't be used because we want to reset the
- ;; narrow region to include the new headings
- (widen)
- (save-excursion
- (goto-char (org-element-property :end ast))
-
- (let (last-absolute-level
- title location relative-level contents
- level)
- (dolist (data output-data)
- (setq title (aref data 0)
- location (aref data 1)
- relative-level (aref data 2)
- contents (aref data 3))
-
- (if (symbolp relative-level)
- (setq level (1+ last-absolute-level))
- (setq last-absolute-level (+ top-level relative-level)
- level last-absolute-level))
-
- (org-noter--insert-heading level title)
-
- (when location
- (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
-
- (when org-noter-doc-property-in-notes
- (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
- (org-entry-put nil org-noter--property-auto-save-last-location "nil"))
-
- (when (car contents)
- (org-noter--insert-heading (1+ level) "Contents")
- (insert (car contents)))
- (when (cdr contents)
- (org-noter--insert-heading (1+ level) "Comment")
- (insert (cdr contents)))))
-
- (setq ast (org-noter--parse-root))
- (org-noter--narrow-to-root ast)
- (goto-char (org-element-property :begin ast))
- (outline-hide-subtree)
- (org-show-children 2)))))
-
- (t (user-error "This command is only supported on PDF Tools.")))))
-
-(defun org-noter-insert-note (&optional precise-info)
- "Insert note associated with the current location.
-
-This command will prompt for a title of the note and then insert
-it in the notes buffer. When the input is empty, a title based on
-`org-noter-default-heading-title' will be generated.
-
-If there are other notes related to the current location, the
-prompt will also suggest them. Depending on the value of the
-variable `org-noter-closest-tipping-point', it may also
-suggest the closest previous note.
-
-PRECISE-INFO makes the new note associated with a more
-specific location (see `org-noter-insert-precise-note' for more
-info).
-
-When you insert into an existing note and have text selected on
-the document buffer, the variable `org-noter-insert-selected-text-inside-note'
-defines if the text should be inserted inside the note."
- (interactive)
- (org-noter--with-valid-session
- (let* ((ast (org-noter--parse-root)) (contents (org-element-contents ast))
- (window (org-noter--get-notes-window 'force))
- (selected-text
- (cond
- ((eq (org-noter--session-doc-mode session) 'pdf-view-mode)
- (when (pdf-view-active-region-p)
- (mapconcat 'identity (pdf-view-active-region-text) ? )))
-
- ((eq (org-noter--session-doc-mode session) 'nov-mode)
- (when (region-active-p)
- (buffer-substring-no-properties (mark) (point))))))
- force-new
- (location (org-noter--doc-approx-location (or precise-info 'interactive) (gv-ref force-new)))
- (view-info (org-noter--get-view-info (org-noter--get-current-view) location)))
-
- (let ((inhibit-quit t))
- (with-local-quit
- (select-frame-set-input-focus (window-frame window))
- (select-window window)
-
- ;; IMPORTANT(nox): Need to be careful changing the next part, it is a bit
- ;; complicated to get it right...
-
- (let ((point (point))
- (minibuffer-local-completion-map org-noter--completing-read-keymap)
- collection default default-begin title selection
- (empty-lines-number (if org-noter-separate-notes-from-heading 2 1)))
-
- (cond
- ;; NOTE(nox): Both precise and without questions will create new notes
- ((or precise-info force-new)
- (setq default (and selected-text (replace-regexp-in-string "\n" " " selected-text))))
- (org-noter-insert-note-no-questions)
- (t
- (dolist (note-cons (org-noter--view-info-notes view-info))
- (let ((display (org-element-property :raw-value (car note-cons)))
- (begin (org-element-property :begin (car note-cons))))
- (push (cons display note-cons) collection)
- (when (and (>= point begin) (> begin (or default-begin 0)))
- (setq default display
- default-begin begin))))))
-
- (setq collection (nreverse collection)
- title (if org-noter-insert-note-no-questions
- default
- (completing-read "Note: " collection nil nil nil nil default))
- selection (unless org-noter-insert-note-no-questions (cdr (assoc title collection))))
-
- (if selection
- ;; NOTE(nox): Inserting on an existing note
- (let* ((note (car selection))
- (insert-before-element (cdr selection))
- (has-content
- (eq (org-element-map (org-element-contents note) org-element-all-elements
- (lambda (element)
- (if (org-noter--check-location-property element)
- 'stop
- (not (memq (org-element-type element) '(section property-drawer)))))
- nil t)
- t)))
- (when has-content (setq empty-lines-number 2))
- (if insert-before-element
- (goto-char (org-element-property :begin insert-before-element))
- (goto-char (org-element-property :end note)))
-
-
- (if (org-at-heading-p)
- (progn
- (org-N-empty-lines-before-current empty-lines-number)
- (forward-line -1))
- (unless (bolp) (insert "\n"))
- (org-N-empty-lines-before-current (1- empty-lines-number)))
-
- (when (and org-noter-insert-selected-text-inside-note selected-text) (insert selected-text)))
-
- ;; NOTE(nox): Inserting a new note
- (let ((reference-element-cons (org-noter--view-info-reference-for-insertion view-info))
- level)
- (when (zerop (length title))
- (setq title (replace-regexp-in-string (regexp-quote "$p$") (number-to-string (car location))
- org-noter-default-heading-title)))
-
- (if reference-element-cons
- (progn
- (cond
- ((eq (car reference-element-cons) 'before)
- (goto-char (org-element-property :begin (cdr reference-element-cons))))
- ((eq (car reference-element-cons) 'after)
- (goto-char (org-element-property :end (cdr reference-element-cons)))))
-
- ;; NOTE(nox): This is here to make the automatic "should insert blank" work better.
- (when (org-at-heading-p) (backward-char))
-
- (setq level (org-element-property :level (cdr reference-element-cons))))
-
- (goto-char (org-element-map contents 'section
- (lambda (section) (org-element-property :end section))
- nil t org-element-all-elements))
- (setq level (1+ (org-element-property :level ast))))
-
- ;; NOTE(nox): This is needed to insert in the right place
- (outline-show-entry)
- (org-noter--insert-heading level title empty-lines-number location)
- (when (org-noter--session-hide-other session) (org-overview))
-
- (setf (org-noter--session-num-notes-in-view session)
- (1+ (org-noter--session-num-notes-in-view session)))))
-
- (org-show-set-visibility t)
- (org-cycle-hide-drawers 'all)
- (org-cycle-show-empty-lines t)))
- (when quit-flag
- ;; NOTE(nox): If this runs, it means the user quitted while creating a note, so
- ;; revert to the previous window.
- (select-frame-set-input-focus (org-noter--session-frame session))
- (select-window (get-buffer-window (org-noter--session-doc-buffer session))))))))
-
-(defun org-noter-insert-precise-note (&optional toggle-no-questions)
- "Insert note associated with a specific location.
-This will ask you to click where you want to scroll to when you
-sync the document to this note. You should click on the top of
-that part. Will always create a new note.
-
-When text is selected, it will automatically choose the top of
-the selected text as the location and the text itself as the
-title of the note (you may change it anyway!).
-
-See `org-noter-insert-note' docstring for more."
- (interactive "P")
- (org-noter--with-valid-session
- (let ((org-noter-insert-note-no-questions (if toggle-no-questions
- (not org-noter-insert-note-no-questions)
- org-noter-insert-note-no-questions)))
- (org-noter-insert-note (org-noter--get-precise-info)))))
-
-
-(defun org-noter-insert-note-toggle-no-questions ()
- "Insert note associated with the current location.
-This is like `org-noter-insert-note', except it will toggle `org-noter-insert-note-no-questions'"
- (interactive)
- (org-noter--with-valid-session
- (let ((org-noter-insert-note-no-questions (not org-noter-insert-note-no-questions)))
- (org-noter-insert-note))))
-
-(defmacro org-noter--map-ignore-headings-with-doc-file (contents match-first &rest body)
- `(let (ignore-until-level)
- (org-element-map ,contents 'headline
- (lambda (headline)
- (let ((doc-file (org-noter--doc-file-property headline))
- (location (org-noter--parse-location-property headline)))
- (when (and ignore-until-level (<= (org-element-property :level headline) ignore-until-level))
- (setq ignore-until-level nil))
-
- (cond
- (ignore-until-level nil) ;; NOTE(nox): This heading is ignored, do nothing
- ((and doc-file (not (string= doc-file (org-noter--session-property-text session))))
- (setq ignore-until-level (org-element-property :level headline)) nil)
- (t ,@body))))
- nil ,match-first org-noter--note-search-no-recurse)))
-
-(defun org-noter-sync-prev-page-or-chapter ()
- "Show previous page or chapter that has notes, in relation to the current page or chapter.
-This will force the notes window to popup."
- (interactive)
- (org-noter--with-valid-session
- (let ((this-location (org-noter--doc-approx-location 0))
- (contents (org-element-contents (org-noter--parse-root)))
- target-location)
- (org-noter--get-notes-window 'force)
-
- (org-noter--map-ignore-headings-with-doc-file
- contents nil
- (when (and (org-noter--compare-locations '< location this-location)
- (org-noter--compare-locations '>f location target-location))
- (setq target-location location)))
-
- (org-noter--get-notes-window 'force)
- (select-window (org-noter--get-doc-window))
- (if target-location
- (org-noter--doc-goto-location target-location)
- (user-error "There are no more previous pages or chapters with notes")))))
-
-(defun org-noter-sync-current-page-or-chapter ()
- "Show current page or chapter notes.
-This will force the notes window to popup."
- (interactive)
- (org-noter--with-valid-session
- (let ((window (org-noter--get-notes-window 'force)))
- (select-frame-set-input-focus (window-frame window))
- (select-window window)
- (org-noter--doc-location-change-handler))))
-
-(defun org-noter-sync-next-page-or-chapter ()
- "Show next page or chapter that has notes, in relation to the current page or chapter.
-This will force the notes window to popup."
- (interactive)
- (org-noter--with-valid-session
- (let ((this-location (org-noter--doc-approx-location most-positive-fixnum))
- (contents (org-element-contents (org-noter--parse-root)))
- target-location)
-
- (org-noter--map-ignore-headings-with-doc-file
- contents nil
- (when (and (org-noter--compare-locations '> location this-location)
- (org-noter--compare-locations '< location target-location))
- (setq target-location location)))
-
- (org-noter--get-notes-window 'force)
- (select-window (org-noter--get-doc-window))
- (if target-location
- (org-noter--doc-goto-location target-location)
- (user-error "There are no more following pages or chapters with notes")))))
-
-(defun org-noter-sync-prev-note ()
- "Go to the location of the previous note, in relation to where the point is.
-As such, it will only work when the notes window exists."
- (interactive)
- (org-noter--with-selected-notes-window
- "No notes window exists"
- (let ((org-noter--inhibit-location-change-handler t)
- (contents (org-element-contents (org-noter--parse-root)))
- (current-begin (org-element-property :begin (org-noter--get-containing-heading)))
- previous)
- (when current-begin
- (org-noter--map-ignore-headings-with-doc-file
- contents t
- (when location
- (if (= current-begin (org-element-property :begin headline))
- t
- (setq previous headline)
- nil))))
-
- (if previous
- (progn
- ;; NOTE(nox): This needs to be manual so we can focus the correct note
- (org-noter--doc-goto-location (org-noter--parse-location-property previous))
- (org-noter--focus-notes-region (org-noter--make-view-info-for-single-note session previous)))
- (user-error "There is no previous note"))))
- (select-window (org-noter--get-doc-window)))
-
-(defun org-noter-sync-current-note ()
- "Go the location of the selected note, in relation to where the point is.
-As such, it will only work when the notes window exists."
- (interactive)
- (org-noter--with-selected-notes-window
- "No notes window exists"
- (if (string= (org-entry-get nil org-noter-property-doc-file t) (org-noter--session-property-text session))
- (let ((location (org-noter--parse-location-property (org-noter--get-containing-heading))))
- (if location
- (org-noter--doc-goto-location location)
- (user-error "No note selected")))
- (user-error "You are inside a different document")))
- (let ((window (org-noter--get-doc-window)))
- (select-frame-set-input-focus (window-frame window))
- (select-window window)))
-
-(defun org-noter-sync-next-note ()
- "Go to the location of the next note, in relation to where the point is.
-As such, it will only work when the notes window exists."
- (interactive)
- (org-noter--with-selected-notes-window
- "No notes window exists"
- (let ((org-noter--inhibit-location-change-handler t)
- (contents (org-element-contents (org-noter--parse-root)))
- next)
-
- (org-noter--map-ignore-headings-with-doc-file
- contents t
- (when (and location (< (point) (org-element-property :begin headline)))
- (setq next headline)))
-
- (if next
- (progn
- (org-noter--doc-goto-location (org-noter--parse-location-property next))
- (org-noter--focus-notes-region (org-noter--make-view-info-for-single-note session next)))
- (user-error "There is no next note"))))
- (select-window (org-noter--get-doc-window)))
-
-(define-minor-mode org-noter-doc-mode
- "Minor mode for the document buffer.
-Keymap:
-\\{org-noter-doc-mode-map}"
- :keymap `((,(kbd "i") . org-noter-insert-note)
- (,(kbd "C-i") . org-noter-insert-note-toggle-no-questions)
- (,(kbd "M-i") . org-noter-insert-precise-note)
- (,(kbd "q") . org-noter-kill-session)
- (,(kbd "M-p") . org-noter-sync-prev-page-or-chapter)
- (,(kbd "M-.") . org-noter-sync-current-page-or-chapter)
- (,(kbd "M-n") . org-noter-sync-next-page-or-chapter)
- (,(kbd "C-M-p") . org-noter-sync-prev-note)
- (,(kbd "C-M-.") . org-noter-sync-current-note)
- (,(kbd "C-M-n") . org-noter-sync-next-note))
-
- (let ((mode-line-segment '(:eval (org-noter--mode-line-text))))
- (if org-noter-doc-mode
- (if (symbolp (car-safe mode-line-format))
- (setq mode-line-format (list mode-line-segment mode-line-format))
- (push mode-line-segment mode-line-format))
- (setq mode-line-format (delete mode-line-segment mode-line-format)))))
-
-(define-minor-mode org-noter-notes-mode
- "Minor mode for the notes buffer.
-Keymap:
-\\{org-noter-notes-mode-map}"
- :keymap `((,(kbd "M-p") . org-noter-sync-prev-page-or-chapter)
- (,(kbd "M-.") . org-noter-sync-current-page-or-chapter)
- (,(kbd "M-n") . org-noter-sync-next-page-or-chapter)
- (,(kbd "C-M-p") . org-noter-sync-prev-note)
- (,(kbd "C-M-.") . org-noter-sync-current-note)
- (,(kbd "C-M-n") . org-noter-sync-next-note)))
+(add-to-list 'load-path (concat (file-name-directory load-file-name) "modules/"))
+(when (or (memq 'doc-view-mode org-noter-supported-modes)
+ (memq 'pdf-view-mode org-noter-supported-modes))
+ (require 'org-noter-pdf))
+(when (memq 'nov-mode org-noter-supported-modes)
+ (require 'org-noter-nov))
+(when (memq 'djvu-read-mode org-noter-supported-modes)
+ (require 'org-noter-djvu))
;;;###autoload
(defun org-noter (&optional arg)
"Start `org-noter' session.
-There are two modes of operation. You may create the session from:
+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 -----------------------------------------
+- 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
+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,
+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
@@ -2107,14 +86,16 @@ With a prefix number ARG:
- 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 ---------------------------------------
+- 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.
+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
+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
@@ -2123,67 +104,88 @@ notes file, even if it finds one."
(cond
;; NOTE(nox): Creating the session from notes file
((eq major-mode 'org-mode)
- (when (org-before-first-heading-p)
- (user-error "`org-noter' must be issued inside a heading"))
-
(let* ((notes-file-path (buffer-file-name))
- (document-property (org-noter--get-or-read-document-property (not (equal arg '(4)))
- (equal arg '(16))))
+ (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))))
-
- (when (catch 'should-continue
- (when (or (numberp arg) (eq arg '-))
- (cond ((> (prefix-numeric-value arg) 0)
- (find-file document-property)
- (throw 'should-continue nil))
- ((< (prefix-numeric-value arg) 0)
- (find-file (file-name-directory document-property))
- (throw 'should-continue nil))))
-
- ;; NOTE(nox): Check if it is an existing session
- (let ((id (get-text-property (org-element-property :begin ast) org-noter--id-text-property))
- session)
- (when id
- (setq session (cl-loop for test-session in org-noter--sessions
- when (= (org-noter--session-id test-session) id)
- return test-session))
- (when session
- (let* ((org-noter--session session)
- (location (org-noter--parse-location-property (org-noter--get-containing-heading))))
- (org-noter--setup-windows session)
- (when location (org-noter--doc-goto-location location))
- (select-frame-set-input-focus (org-noter--session-frame session)))
- (throw 'should-continue nil))))
- t)
+ (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 '(doc-view-mode pdf-view-mode nov-mode))
+ ;;
+ ;; eg: M-x org-noter from a pdf document
+ ((memq major-mode org-noter-supported-modes)
+ ;; if an org-noter sesseion already exists
(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)))
+ (run-hook-with-args-until-success 'org-noter-create-session-from-document-hook arg buffer-file-name)))))
+
+(defun org-noter--create-session-from-document-file-default (&optional arg document-file-name)
+ "Create a new org-noter session from an open document file.
+This is the default implementation that is called by
+`org-noter-create-session-from-document-hook`.
+ARG is the prefix argument passed to `org-noter`
+DOCUMENT-FILE-NAME is the document filename."
;; 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 buffer-file-name (bound-and-true-p nov-file-name)))
- (document-path (or buffer-file-name buffer-file-truename
+ ;; `document-file-name', and may not have the same results
+ (let* ((document-file-name (or (run-hook-with-args-until-success 'org-noter-get-buffer-file-name-hook major-mode)
+ document-file-name))
+ (document-path (or document-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)
+ (document-directory (if document-file-name
+ (file-name-directory document-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
+ ;; be the same as `document-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"))))
- notes-files-annotating ; List of files annotating document
- notes-files ; List of found notes files (annotating or not)
+ (search-names (remove nil (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)))
@@ -2225,8 +227,7 @@ notes file, even if it finds one."
(when (file-exists-p file-name)
(setq file-name (propertize file-name 'display
(concat file-name
- (propertize " -- Exists!"
- 'face '(foreground-color . "green")))))
+ (propertize " -- Exists!" 'face '(:foregorund "green")))))
(push file-name list-of-possible-targets)
(throw 'break nil))
@@ -2246,8 +247,7 @@ notes file, even if it finds one."
(when (file-exists-p file-name)
(setq file-name (propertize file-name 'display
(concat file-name
- (propertize " -- Exists!"
- 'face '(foreground-color . "green"))))))
+ (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
@@ -2267,27 +267,46 @@ notes file, even if it finds one."
(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-noter-headline-title-decoration
+ document-base
+ org-noter-headline-title-decoration)
(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 (cl-delete-duplicates notes-files-annotating :test 'equal)) 1)
+ (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-wide-buffer
- (catch 'break
- (goto-char (point-min))
- (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))
- (throw 'break t)))))))))))
+ (org-with-point-at (point-min)
+ (catch 'break
+ (while (re-search-forward (org-re-property org-noter-property-doc-file) nil)
+ (when (file-equal-p (expand-file-name (match-string 3)
+ (file-name-directory (car notes-files-annotating)))
+ document-path)
+ (if-let ((saved-location (org-entry-get nil org-noter-property-note-location)))
+ (setq document-location (cons (string-to-number saved-location) 0)))
+ (let ((org-noter--start-location-override document-location))
+ (org-noter arg))
+ (throw 'break t))))))))
+
+;;;###autoload
+(defun org-noter-start-from-dired ()
+ "In Dired, open sessions for marked files or file at point.
+
+If there are multiple marked files, focus will be on the last
+marked file."
+ (interactive)
+ (let ((files (or (dired-get-marked-files)
+ (dired-get-filename))))
+ (dolist (filename files)
+ (find-file filename)
+ (save-excursion (org-noter))
+ (bury-buffer))
+ (other-frame 1)))
(provide 'org-noter)
diff --git a/other/org-noter-citar.el b/other/org-noter-citar.el
new file mode 100644
index 0000000..3fc6f2a
--- /dev/null
+++ b/other/org-noter-citar.el
@@ -0,0 +1,87 @@
+;;; org-noter-citar.el --- Module for finding note files from `citar' -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 c1-g
+
+;; Author: c1-g
+;; Keywords: convenience
+
+;; 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 .
+
+;;; Code:
+(require 'citar)
+(require 'org-ref)
+(require 'seq)
+
+;; Regexp stolen from org-roam-bibtex; orb-utils-citekey-re.
+(defvar org-noter-citar-cite-key-re
+ (rx
+ (or
+ (seq (group-n 2 (regexp
+ ;; If Org-ref is available, use its types
+ ;; default to "cite"
+ (if (boundp 'org-ref-cite-types)
+ (regexp-opt
+ (mapcar
+ (lambda (el)
+ ;; Org-ref v3 cite type is a list of strings
+ ;; Org-ref v2 cite type is a plain string
+ (or (car-safe el) el))
+ org-ref-cite-types))
+ "cite")))
+ ":"
+ (or
+ ;; Org-ref v2 style `cite:links'
+ (group-n 1 (+ (any "a-zA-Z0-9_:.-")))
+ ;; Org-ref v3 style `cite:Some&key'
+ (seq (*? (not "&")) "&"
+ (group-n 1 (+ (any "!#-+./:<>-@^-`{-~-" word))))))
+ ;; Org-cite [cite/@citations]
+ (seq "@" (group-n 1 (+ (any "!#-+./:<>-@^-`{-~-" word))))))
+ "Universal regexp to match citations in ROAM_REFS.
+
+Supports Org-ref v2 and v3 and Org-cite.")
+
+(defun org-noter-citar-find-document-from-refs (cite-key)
+ "Return a note file associated with CITE-KEY.
+When there is more than one note files associated with CITE-KEY, have
+user select one of them."
+ (when (and (stringp cite-key) (string-match org-noter-citar-cite-key-re cite-key))
+ (let* ((key (match-string 1 cite-key))
+ (entries (citar--ensure-entries (list key)))
+ (files (citar-file--files-for-multiple-entries
+ entries
+ (append citar-library-paths citar-notes-paths) nil))
+ (url (list (citar-get-link (car entries))))
+ (documents (flatten-list (append (seq-remove #'file-directory-p files) url))))
+ (cond ((= (length documents) 1)
+ (car documents))
+ ((> (length documents) 1)
+ (completing-read (format "Which document from %s?: " key) documents))))))
+
+(defun org-noter-citar-find-key-from-this-file (filename)
+ (let* ((entry-alist (mapcan (lambda (entry)
+ (when-let ((file (citar-get-value citar-file-variable entry)))
+ (list (cons file (citar-get-value "=key=" entry)))))
+ (citar--get-candidates)))
+ (key (alist-get filename entry-alist nil nil (lambda (s regexp)
+ (string-match-p regexp s)))))
+ (when key
+ (file-name-with-extension key "org"))))
+
+(add-to-list 'org-noter-parse-document-property-hook #'org-noter-citar-find-document-from-refs)
+
+(add-to-list 'org-noter-find-additional-notes-functions #'org-noter-citar-find-key-from-this-file)
+
+(provide 'org-noter-citar)
+;;; org-noter-citar.el ends here
diff --git a/other/org-noter-dynamic-block.el b/other/org-noter-dynamic-block.el
new file mode 100644
index 0000000..a2878f6
--- /dev/null
+++ b/other/org-noter-dynamic-block.el
@@ -0,0 +1,212 @@
+;;; org-noter-dynamic-block.el --- Use special blocks as notes -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 c1-g
+
+;; Author: c1-g
+;; Keywords: multimedia
+
+;; 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 .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'org-noter-core)
+
+(defun org-noter-insert-precise-dynamic-block (&optional toggle-no-questions)
+ "Insert note associated with a specific location.
+This will ask you to click where you want to scroll to when you
+sync the document to this note. You should click on the top of
+that part. Will always create a new note.
+
+When text is selected, it will automatically choose the top of
+the selected text as the location and the text itself as the
+title of the note (you may change it anyway!).
+
+See `org-noter-insert-note' docstring for more."
+ (interactive "P")
+ (org-noter--with-valid-session
+ (let ((org-noter-insert-note-no-questions (if toggle-no-questions
+ (not org-noter-insert-note-no-questions)
+ org-noter-insert-note-no-questions)))
+ (org-noter-insert-dynamic-block (org-noter--get-precise-info)))))
+
+(defun org-noter-insert-dynamic-block (&optional precise-info)
+ "Insert note associated with the current location.
+
+This command will prompt for a title of the note and then insert
+it in the notes buffer. When the input is empty, a title based on
+`org-noter-default-heading-title' will be generated.
+
+If there are other notes related to the current location, the
+prompt will also suggest them. Depending on the value of the
+variable `org-noter-closest-tipping-point', it may also
+suggest the closest previous note.
+
+PRECISE-INFO makes the new note associated with a more
+specific location (see `org-noter-insert-precise-note' for more
+info).
+
+When you insert into an existing note and have text selected on
+the document buffer, the variable `org-noter-insert-selected-text-inside-note'
+defines if the text should be inserted inside the note."
+ (interactive)
+ (org-noter--with-valid-session
+ (let* ((ast (org-noter--parse-root))
+ (contents (org-element-contents ast))
+ (window (org-noter--get-notes-window 'force))
+ (selected-text
+ (pcase (org-noter--session-doc-mode session)
+ ('pdf-view-mode
+ (when (pdf-view-active-region-p)
+ (mapconcat 'identity (pdf-view-active-region-text) ? )))
+
+ ((or 'nov-mode 'djvu-read-mode)
+ (when (region-active-p)
+ (buffer-substring-no-properties (mark) (point))))))
+
+ force-new
+ (location (org-noter--doc-approx-location (or precise-info 'interactive) (gv-ref force-new)))
+ (view-info (org-noter--get-view-info (org-noter--get-current-view) location)))
+
+ (let ((inhibit-quit t))
+ (with-local-quit
+ (select-frame-set-input-focus (window-frame window))
+ (select-window window)
+
+ ;; IMPORTANT(nox): Need to be careful changing the next part, it is a bit
+ ;; complicated to get it right...
+
+ (let ((point (point))
+ (minibuffer-local-completion-map org-noter--completing-read-keymap)
+ collection default default-begin title
+ (empty-lines-number (if org-noter-separate-notes-from-heading 2 1)))
+
+ (cond
+ ;; NOTE(nox): Both precise and without questions will create new notes
+ ((or precise-info force-new)
+ (setq default (and selected-text (replace-regexp-in-string "\n" " " selected-text))))
+ (org-noter-insert-note-no-questions)
+ (t
+ (dolist (note-cons (org-noter--view-info-notes view-info))
+ (let ((display (org-element-property :raw-value (car note-cons)))
+ (begin (org-element-property :begin (car note-cons))))
+ (push (cons display note-cons) collection)
+ (when (and (>= point begin) (> begin (or default-begin 0)))
+ (setq default display
+ default-begin begin))))))
+
+ ;; NOTE(nox): Inserting a new note
+ (let ((reference-element-cons (org-noter--view-info-reference-for-insertion view-info))
+ level)
+
+ (if reference-element-cons
+ (progn
+ (cond
+ ((eq (car reference-element-cons) 'before)
+ (goto-char (org-element-property :begin (cdr reference-element-cons))))
+ ((eq (car reference-element-cons) 'after)
+ (goto-char (org-element-property :end (cdr reference-element-cons)))))
+ ;; NOTE(nox): This is here to make the automatic "should insert blank" work better.
+ (when (org-at-heading-p) (backward-char))
+ (setq level (org-element-property :level (cdr reference-element-cons))))
+
+ (goto-char (or (org-element-map contents 'section
+ (lambda (section) (org-element-property :end section))
+ nil t org-element-all-elements)
+ (org-element-map ast 'section
+ (lambda (section) (org-element-property :end section))
+ nil t org-element-all-elements))))
+
+ ;; (setq level (1+ (or (org-element-property :level ast) 0))))
+
+ ;; NOTE(nox): This is needed to insert in the right place
+ (unless (org-noter--no-heading-p) (outline-show-entry))
+ ;; (org-noter--insert-heading level title empty-lines-number location)
+ (insert
+ "\n"
+ (string-join (list (format "#+BEGIN: note %s"
+ (if location
+ (concat ":" org-noter-property-note-location
+ (format " %S" location))
+ ""))
+ (or selected-text "")
+ "#+END:")
+ "\n")
+ "\n")
+
+ (when (org-noter--session-hide-other session) (org-overview))
+
+ (setf (org-noter--session-num-notes-in-view session)
+ (1+ (org-noter--session-num-notes-in-view session)))))
+
+ (org-show-set-visibility t)
+ (org-cycle-hide-drawers 'all)
+ (org-cycle-show-empty-lines t)))
+
+ (when quit-flag
+ ;; NOTE(nox): If this runs, it means the user quitted while creating a note, so
+ ;; revert to the previous window.
+ (select-frame-set-input-focus (org-noter--session-frame session))
+ (select-window (get-buffer-window (org-noter--session-doc-buffer session)))))))
+
+(defun org-dblock-write:note (params)
+ (let ((location (plist-get params
+ (intern (concat ":" org-noter-property-note-location))))
+ (content (plist-get params :content))
+ (session org-noter--session)
+ (origin-window (selected-window))
+ (origin-location))
+
+ (org-noter--with-valid-session
+ (setq origin-location (org-noter--doc-approx-location))
+ (when (and location
+ (org-noter--get-location-top location)
+ (org-noter--get-location-left location))
+ (org-noter--doc-goto-location location)
+ (with-current-buffer (org-noter--session-doc-buffer session)
+ (setq content
+ (pcase major-mode
+ ('pdf-view-mode (pdf-info-gettext (car location) (cdr location)))
+ ((or 'nov-mode 'djvu-read-mode)
+ (buffer-substring (org-noter--get-location-top location)
+ (org-noter--get-location-left location))))))
+ (org-noter--doc-goto-location origin-location)
+ (select-window origin-window)))
+ (insert content)))
+
+(defun org-noter--get-location-dynamic-block (dblock)
+ (let ((params (read (concat "(" (org-element-property :arguments dblock) ")"))))
+ (format "%S" (plist-get params (intern (concat ":" org-noter-property-note-location))))))
+
+(defun org-noter-get-containing-dynamic-block (&optional _include-root)
+ (org-noter--with-valid-session
+ (org-with-wide-buffer
+ (let ((elt (org-element-at-point)))
+ (catch 'break
+ (while (org-element-property :parent elt)
+ (cond
+ ((eq (org-element-type elt) 'dynamic-block)
+ (throw 'break elt))
+ (t
+ (setq elt (org-element-property :parent elt))))))))))
+
+(add-hook 'org-noter--get-containing-element-hook #'org-noter-get-containing-dynamic-block)
+
+(add-hook 'org-noter--get-location-property-hook #'org-noter--get-location-dynamic-block)
+
+(provide 'org-noter-dynamic-block)
+;;; org-noter-dynamic-block.el ends here
+
diff --git a/other/org-noter-integration.el b/other/org-noter-integration.el
index 6cee0cd..496148c 100644
--- a/other/org-noter-integration.el
+++ b/other/org-noter-integration.el
@@ -1,67 +1,110 @@
-(require 'org-noter)
+;;; org-noter-pdftools.el --- Integration between org-pdftools and org-noter
+;; Copyright (C) 2020 Alexander Fu Xi
+
+;; Author: Alexander Fu Xi
+;; Maintainer: Alexander Fu Xi
+;; Homepage: https://github.com/fuxialexander/org-pdftools
+;; Version: 1.0
+;; Keywords: convenience
+;; Package-Requires: ((emacs "26.1") (org "9.4") (pdf-tools "0.8") (org-pdftools "1.0") (org-noter "1.4.1"))
+
+;; 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 .
+
+;;; Commentary:
+;; Add integration between org-pdftools and org-noter.
+
+
+;;; Code:
+(require 'org-id)
(require 'org-pdftools)
+(require 'org-noter)
+(require 'image-mode)
-(declare-function pdf-info-editannots "ext:pdf-info")
+(declare-function pdf-info-editannot "ext:pdf-info")
(declare-function pdf-annot-add-text-annotation "ext:pdf-annot")
(declare-function pdf-annot-get-id "ext:pdf-annot")
-(defcustom org-noter-markup-pointer-function 'pdf-annot-add-highlight-markup-annotation
+(defcustom org-noter-pdftools-markup-pointer-function 'pdf-annot-add-highlight-markup-annotation
"Color for markup pointer annotations.
Can be one of highlight/underline/strikeout/squiggly."
:group 'org-noter
:type 'function)
-(defcustom org-noter-markup-pointer-color "#A9A9A9"
- "Color for markup pointer annotations"
+(defcustom org-noter-pdftools-path-generator #'abbreviate-file-name
+ "Translate your PDF file path the way you like. Take buffer-file-name as the argument."
+ :group 'org-pdftools
+ :type 'function)
+
+(defcustom org-noter-pdftools-markup-pointer-color "#A9A9A9"
+ "Color for markup pointer annotations."
:group 'org-noter
:type 'string)
-(defcustom org-noter-markup-pointer-opacity 1.0
- "Color for markup pointer annotations"
+(defcustom org-noter-pdftools-markup-pointer-opacity 1.0
+ "Color for markup pointer annotations."
:group 'org-noter
:type 'float)
-(defcustom org-noter-free-pointer-icon "Circle"
+(defcustom org-noter-pdftools-free-pointer-icon "Circle"
"Color for free pointer annotations. Refer to `pdf-annot-standard-text-icons`."
:group 'org-noter
:type 'string)
-(defcustom org-noter-free-pointer-color "#FFFFFF"
- "Color for free pointer annotations"
+(defcustom org-noter-pdftools-free-pointer-color "#FFFFFF"
+ "Color for free pointer annotations."
:group 'org-noter
:type 'string)
-(defcustom org-noter-free-pointer-opacity 1.0
- "Color for free pointer annotations"
+(defcustom org-noter-pdftools-free-pointer-opacity 1.0
+ "Color for free pointer annotations."
:group 'org-noter
:type 'float)
-(defcustom org-noter-use-pdftools-link-location t
+(defcustom org-noter-pdftools-use-pdftools-link-location t
"When non-nil, org-pdftools link is used instead of location-cons when inserting notes."
:group 'org-noter
:type 'boolean)
-(defcustom org-noter-use-org-id t
+(defcustom org-noter-pdftools-use-org-id t
"When non-nil, an org-id is generated for each heading for linking with PDF annotations and record entry parents."
:group 'org-noter
:type 'boolean)
-(defcustom org-noter-export-to-pdf t
- "When non-nil, PDF annotation contents will include both org-id of original notes and org-id of its parent.
+(defcustom org-noter-pdftools-export-to-pdf t
+ "TODO: Whether you want to export the org notes to pdf annotation contents.
+To use this, `org-noter-pdftools-use-org-id' has to be t."
+ :group 'org-noter
+ :type 'boolean)
-To use this, `org-noter-use-org-id' has to be t."
+(defcustom org-noter-pdftools-export-to-pdf-with-structure t
+ "TODO: Whether you want to export the org notes to pdf annotation contents.
+To use this, `org-noter-pdftools-use-org-id' has to be t."
:group 'org-noter
:type 'boolean)
-(defcustom org-noter-export-to-pdf-with-structure t
- "When non-nil, PDF annotation contents will include both org-id of original notes and org-id of its parent.
+(defcustom org-noter-pdftools-use-unique-org-id t
+ "When non-nil, an org-id is generated for each heading for linking with PDF annotations and record entry parents."
+ :group 'org-noter
+ :type 'boolean)
-To use this, `org-noter-use-org-id' has to be t."
+(defcustom org-noter-pdftools-insert-content-heading t
+ "When non-nil, insert a \"Content\" heading above the content of an annotation (underline, highlight)"
:group 'org-noter
:type 'boolean)
-(defcustom org-noter-use-unique-org-id t
- "When non-nil, an org-id is generated for each heading for linking with PDF annotations and record entry parents."
+(defcustom org-noter-pdftools-insert-comment-heading t
+ "When non-nil, insert a \"Content\" heading above the content of an annotation (underline, highlight)"
:group 'org-noter
:type 'boolean)
@@ -69,61 +112,91 @@ To use this, `org-noter-use-org-id' has to be t."
path page height annot-id search-string original-property)
(defun org-noter-pdftools--location-link-p (location)
+ "Check whether LOCATION is a org-pdftools link."
(and location
(stringp location)
- (string-prefix-p "pdftools:" location)))
-
-(defun org-noter--location-cons-to-link (location)
+ (or
+ (string-prefix-p
+ (concat "[[" org-pdftools-link-prefix ":")
+ location)
+ (string-prefix-p
+ (concat org-pdftools-link-prefix ":")
+ location))))
+
+(defun org-noter-pdftools--location-cons-to-link (location)
+ "Convert LOCATION cons to link."
(cond ((consp location)
(concat
"::"
(number-to-string
(car location))
"++"
- (format "%.2f" (cdr location))))
+ (format "%.2f" (cadr location))))
((integerp location)
(concat
"::"
(number-to-string
(car location))))))
-(defun org-noter--location-link-to-cons (location)
- "Convert a org-pdftools link to old location cons."
+(defun org-noter-pdftools--location-link-to-cons (location)
+ "Convert a org-pdftools link to old LOCATION cons."
(cons (org-noter-pdftools--location-page location) (or (org-noter-pdftools--location-height location) 0.0)))
;; --------------------------------------------------------------------------------
;; NOTE(nox): Interface
(defun org-noter-pdftools--check-link (property)
+ "Interface for checking PROPERTY link."
(org-noter-pdftools--location-link-p property))
(defun org-noter-pdftools--parse-link (property)
+ "Interface for parse PROPERTY link."
(when (org-noter-pdftools--location-link-p property)
- (string-match "\\(.*\\)::\\([0-9]*\\)\\(\\+\\+\\)?\\([[0-9]\\.*[0-9]*\\)?\\(;;\\|\\$\\$\\)?\\(.*\\)?" property)
- (let ((path (match-string 1 property))
- (page (match-string 2 property))
- (height (match-string 4 property))
- annot-id search-string)
- (cond ((string-equal (match-string 5 property) ";;")
- (setq annot-id (match-string 6 property)))
- ((string-equal (match-string 5 property) "$$")
- (setq search-string (replace-regexp-in-string "%20" " " (match-string 6 property)))))
- (make-org-noter-pdftools--location
- :path path
- :page (and page (string-to-number page))
- :height (and height (string-to-number height))
- :annot-id annot-id
- :search-string search-string
- :original-property property))))
+ (setq property (string-trim property "\\[\\[" "\\]\\]"))
+ (let ((link-regexp (concat "\\(.*\\)::\\([0-9]*\\)\\(\\+\\+\\)?\\([[0-9]\\.*[0-9]*\\)?\\(;;\\|"
+ (regexp-quote org-pdftools-search-string-separator)
+ "\\)?\\(.*\\)?")))
+ (string-match link-regexp property)
+ (let ((path (match-string 1 property))
+ (page (match-string 2 property))
+ (height (match-string 4 property))
+ annot-id search-string)
+ (condition-case nil
+ (cond ((string-equal (match-string 5 property) ";;")
+ (setq annot-id (match-string 6 property)))
+ ((string-equal (match-string 5 property) org-pdftools-search-string-separator)
+ (setq search-string (replace-regexp-in-string "%20" " " (match-string 6 property)))))
+ (error nil))
+ (make-org-noter-pdftools--location
+ :path path
+ :page (and page (string-to-number page))
+ :height (and height (string-to-number height))
+ :annot-id annot-id
+ :search-string search-string
+ :original-property property)))))
(defun org-noter-pdftools--pretty-print-location (location)
- (and (org-noter-pdftools--location-p location)
- (org-noter-pdftools--location-original-property location)))
+ "Function for print the LOCATION link."
+ (org-noter--with-valid-session
+ (if (memq (org-noter--session-doc-mode session) '(doc-view-mode pdf-view-mode))
+ (let ((loc (if (org-noter-pdftools--location-p location)
+ location
+ (org-noter-pdftools--parse-link location))))
+ (concat "[["
+ (org-noter-pdftools--location-original-property loc)
+ "]]"))
+ nil)))
(defun org-noter-pdftools--convert-to-location-cons (location)
- (when (org-noter-pdftools--location-p location)
- (org-noter--location-link-to-cons location)))
-
-(defun org-noter-pdftools--doc-goto-location (mode location)
+ "Function for converting the LOCATION link to cons."
+ (if (and location (consp location))
+ location
+ (let ((loc (if (org-noter-pdftools--location-p location)
+ location
+ (org-noter-pdftools--parse-link location))))
+ (org-noter-pdftools--location-link-to-cons loc))))
+
+(defun org-noter-pdftools--doc-goto-location (mode location &optional _window)
+ "Goto LOCATION in the corresponding MODE."
(when (and (eq mode 'pdf-view-mode) (org-noter-pdftools--location-p location))
(when (org-noter-pdftools--location-page location)
(pdf-view-goto-page (org-noter-pdftools--location-page location)))
@@ -139,29 +212,33 @@ To use this, `org-noter-use-org-id' has to be t."
t))
(defun org-noter-pdftools--note-after-tipping-point (point location view)
+ "Call `org-noter--note-after-tipping-point' relative to POINT based on LOCATION and VIEW."
(when (org-noter-pdftools--location-p location)
- (cons t (org-noter--note-after-tipping-point point (org-noter--location-link-to-cons location) view))))
+ (cons t (org-noter--note-after-tipping-point point (org-noter-pdftools--location-link-to-cons location) view))))
(defun org-noter-pdftools--relative-position-to-view (location view)
+ "Get relative position based on LOCATION and VIEW."
(when (org-noter-pdftools--location-p location)
- (org-noter--relative-position-to-view (org-noter--location-link-to-cons location) view)))
+ (org-noter--relative-position-to-view (org-noter-pdftools--location-link-to-cons location) view)))
-(defun org-noter-pdftools--get-precise-info (mode)
+(defun org-noter-pdftools--get-precise-info (mode &optional _window)
+ "Get precise info from MODE."
(when (eq mode 'pdf-view-mode)
- (let ((org-pdftools-free-pointer-icon org-noter-free-pointer-icon)
- (org-pdftools-free-pointer-color org-noter-free-pointer-color)
- (org-pdftools-free-pointer-opacity org-noter-free-pointer-opacity)
- (org-pdftools-markup-pointer-color org-noter-markup-pointer-color)
- (org-pdftools-markup-pointer-opacity org-noter-markup-pointer-opacity)
- (org-pdftools-markup-pointer-function org-noter-markup-pointer-function))
- (org-noter-pdftools--parse-link (org-pdftools-get-link t)))))
+ (let ((org-pdftools-free-pointer-icon org-noter-pdftools-free-pointer-icon)
+ (org-pdftools-free-pointer-color org-noter-pdftools-free-pointer-color)
+ (org-pdftools-free-pointer-opacity org-noter-pdftools-free-pointer-opacity)
+ (org-pdftools-markup-pointer-color org-noter-pdftools-markup-pointer-color)
+ (org-pdftools-markup-pointer-opacity org-noter-pdftools-markup-pointer-opacity)
+ (org-pdftools-markup-pointer-function org-noter-pdftools-markup-pointer-function))
+ (org-noter-pdftools--parse-link (org-pdftools-get-link)))))
(defun org-noter-pdftools--doc-approx-location (mode precise-info force-new-ref)
+ "Get approximate location in MODE buffer based on PRECISE-INFO and FORCE-NEW-REF."
(org-noter--with-valid-session
(when (eq mode 'pdf-view-mode)
(cond ((or (numberp precise-info) (not precise-info))
(org-noter-pdftools--parse-link
- (concat "pdftools:" (expand-file-name (org-noter--session-property-text session)) "::"
+ (concat org-pdftools-link-prefix ":" (expand-file-name (org-noter--session-property-text session)) "::"
(number-to-string (image-mode-window-get 'page))
(when precise-info (concat "++" (number-to-string precise-info))))))
((org-noter-pdftools--location-p precise-info) precise-info)
@@ -172,18 +249,22 @@ To use this, `org-noter-use-org-id' has to be t."
(t (error "Invalid pdftools precise-info case: %s" precise-info))))))
(defun org-noter-pdftools--insert-heading ()
- (let ((location-property (org-entry-get nil org-noter-property-note-location)))
- (when (string-match ".*;;\\(.*\\)" location-property)
- (org-noter--with-valid-session
- (let ((id (match-string 1 location-property)))
- (if org-noter-use-org-id
- (org-entry-put nil "ID"
- (if org-noter-use-unique-org-id
- (concat
- (org-noter--session-property-text session)
- "-"
- id)
- id))))))))
+ "Insert heading in the `org-noter' org document."
+ (let* ((location-property (org-entry-get nil org-noter-property-note-location)))
+ (when location-property
+ (if (string-suffix-p "]]" location-property)
+ (setq location-property (substring location-property 0 -2)))
+ (when (string-match ".*;;\\(.*\\)" location-property)
+ (org-noter--with-valid-session
+ (let ((id (match-string 1 location-property)))
+ (if org-noter-pdftools-use-org-id
+ (org-entry-put nil "ID"
+ (if org-noter-pdftools-use-unique-org-id
+ (concat
+ (org-noter--session-property-text session)
+ "-"
+ id)
+ id)))))))))
(dolist (pair '((org-noter--check-location-property-hook . org-noter-pdftools--check-link)
(org-noter--parse-location-property-hook . org-noter-pdftools--parse-link)
@@ -199,7 +280,7 @@ To use this, `org-noter-use-org-id' has to be t."
;; --------------------------------------------------------------------------------
;; NOTE(nox): User commands
-(defun org-noter-convert-old-org-heading ()
+(defun org-noter-pdftools-convert-old-org-heading ()
"Covert an old org heading to a new one for compatiblility."
(interactive)
(org-noter--with-valid-session
@@ -208,7 +289,7 @@ To use this, `org-noter-use-org-id' has to be t."
'pdf-view-mode)
(let* ((document-property (org-noter--session-property-text
session)))
- (let* ((location (org-noter--location-property
+ (let* ((location (org-noter--parse-location-property
(org-entry-get
nil
org-noter-property-note-location)))
@@ -217,7 +298,7 @@ To use this, `org-noter-use-org-id' has to be t."
(car location)
location))
(height (if (consp location)
- (cdr location)
+ (cadr location)
0.0))
(pos `(0 . ,(round
(*
@@ -242,25 +323,28 @@ To use this, `org-noter-use-org-id' has to be t."
nil
org-noter-property-note-location
(concat
- "pdftools:"
+ "[["
+ org-pdftools-link-prefix
+ ":"
path
- (org-noter--location-cons-to-link
+ (org-noter-pdftools--location-cons-to-link
location)
";;"
- annot-id))
- (when org-noter-use-org-id
+ annot-id
+ "]]"))
+ (when org-noter-pdftools-use-org-id
(org-entry-put
nil
"ID"
- (if org-noter-use-unique-org-id
+ (if org-noter-pdftools-use-unique-org-id
(concat
document-property
"-"
annot-id)
annot-id)))
- (when org-noter-export-to-pdf
+ (when org-noter-pdftools-export-to-pdf
(let* ((content (if (and (> (org-current-level) 2)
- org-noter-export-to-pdf-with-structure)
+ org-noter-pdftools-export-to-pdf-with-structure)
(let ((parent-id (save-excursion
(org-up-heading-safe)
(org-id-get))))
@@ -288,7 +372,7 @@ To use this, `org-noter-use-org-id' has to be t."
(error
"This command is only supported on PDF Tools")))))
-(defun org-noter-convert-old-notes ()
+(defun org-noter-pdftools-convert-old-notes ()
"Convert old notes (location cons based) to new format (link based)."
(interactive)
(org-noter--with-valid-session
@@ -303,45 +387,36 @@ To use this, `org-noter-use-org-id' has to be t."
org-noter-property-note-location)))
(if (and prop
(not (string-prefix-p
- "pdftools:"
+ org-pdftools-link-prefix ":"
prop)))
(call-interactively
- #'org-noter-convert-old-org-heading))))))
+ #'org-noter-pdftools-convert-old-org-heading))))))
-(defun org-noter-jump-to-note (a)
+(defun org-noter-pdftools-jump-to-note (a)
"Jump from a PDF annotation A to the corresponding org heading."
(interactive (list
(with-selected-window
(org-noter--get-doc-window)
(pdf-annot-read-annotation
"Left click the annotation "))))
- (when (not org-noter-use-org-id)
- "You have to enable `org-noter-use-org-id'!")
+ (unless org-noter-pdftools-use-org-id
+ "You have to enable `org-noter-pdftools-use-org-id'!")
(org-noter--with-valid-session
(pdf-annot-show-annotation a t)
(let ((id (symbol-name
(pdf-annot-get-id a))))
(select-window
(org-noter--get-notes-window))
- (condition-case-unless-debug
- nil
- (progn
- (require 'org-id)
- (goto-char
- (cdr (org-id-find-id-in-file
- (if org-noter-use-unique-org-id
- (concat
- (org-noter--session-property-text
- session)
- "-"
- id)
- id)
- buffer-file-name))))
- (error nil))
- t)))
+ (let ((exist-id (org-id-find-id-in-file
+ (if org-noter-pdftools-use-unique-org-id
+ (concat (org-noter--session-property-text session) "-" id)
+ id)
+ buffer-file-name)))
+ (if exist-id (goto-char (cdr exist-id))
+ nil)))))
;; TODO(nox): Implement interface for skeleton creation
-(defun org-noter-create-skeleton ()
+(defun org-noter-pdftools-create-skeleton ()
"Create notes skeleton with the PDF outline or annotations.
Only available with PDF Tools."
(interactive)
@@ -367,29 +442,28 @@ Only available with PDF Tools."
pdftools-link path)
(when (and (eq type 'goto-dest)
(> page 0))
- (when org-noter-use-pdftools-link-location
- (setq path (file-relative-name
- (expand-file-name
- (org-noter--session-property-text
- session))
- org-pdftools-root-dir))
+ (when org-noter-pdftools-use-pdftools-link-location
+ (setq path
+ (funcall org-noter-pdftools-path-generator (buffer-file-name)))
(if title
(setq pdftools-link
(concat
- "pdftools:"
+ org-pdftools-link-prefix ":"
path
"::"
(number-to-string page)
"++"
- (number-to-string top)
- "$$"
+ (if top
+ (number-to-string top)
+ "0")
+ org-pdftools-search-string-separator
(replace-regexp-in-string
" "
"%20"
title)))
(setq pdftools-link
(concat
- "pdftools:"
+ org-pdftools-link-prefix ":"
path
"::"
(number-to-string page)
@@ -398,7 +472,7 @@ Only available with PDF Tools."
(push
(vector
title
- (if org-noter-use-pdftools-link-location pdftools-link
+ (if org-noter-pdftools-use-pdftools-link-location pdftools-link
(cons page top))
(1+ depth)
nil)
@@ -439,16 +513,11 @@ Only available with PDF Tools."
(top (nth 1 edges))
(item-subject (alist-get 'subject item))
(item-contents (alist-get 'contents item))
- name contents pdftools-link id path)
- (when org-noter-use-pdftools-link-location
- (setq path
- (file-relative-name
- (expand-file-name
- (org-noter--session-property-text
- session))
- org-pdftools-root-dir))
- (setq id (symbol-name (alist-get 'id item)))
- (setq pdftools-link (concat "pdftools:" path "::"
+ (id (symbol-name (alist-get 'id item)))
+ name contents pdftools-link path)
+ (when org-noter-pdftools-use-pdftools-link-location
+ (setq path (funcall org-noter-pdftools-path-generator (buffer-file-name)))
+ (setq pdftools-link (concat org-pdftools-link-prefix ":" path "::"
(number-to-string page) "++"
(number-to-string top) ";;"
id)))
@@ -470,7 +539,7 @@ Only available with PDF Tools."
(if (and item-subject item-contents) "\n" "")
(or item-contents ""))))))
- (push (vector (format "%s on page %d" name page) (if org-noter-use-pdftools-link-location
+ (push (vector (format "%s on page %d" name page) (if org-noter-pdftools-use-pdftools-link-location
pdftools-link
(cons page top)) 'inside contents)
output-data)))))
@@ -486,14 +555,10 @@ Only available with PDF Tools."
(top (nth 1 edges))
(target-page (alist-get 'page link))
target heading-text pdftools-link path)
- (when org-noter-use-pdftools-link-location
+ (when org-noter-pdftools-use-pdftools-link-location
(setq path
- (file-relative-name
- (expand-file-name
- (org-noter--session-property-text
- session))
- org-pdftools-root-dir))
- (setq pdftools-link (concat "pdftools:" path "::"
+ (funcall org-noter-pdftools-path-generator (buffer-file-name)))
+ (setq pdftools-link (concat org-pdftools-link-prefix ":" path "::"
(number-to-string page) "++"
(number-to-string top))))
(unless (and title (> (length title) 0)) (setq title (pdf-info-gettext page edges)))
@@ -514,7 +579,7 @@ Only available with PDF Tools."
(push
(vector
heading-text
- (if org-noter-use-pdftools-link-location
+ (if org-noter-pdftools-use-pdftools-link-location
pdftools-link
(cons page top))
'inside
@@ -558,10 +623,12 @@ Only available with PDF Tools."
(org-noter--insert-heading level title nil location)
(when (car contents)
- (org-noter--insert-heading (1+ level) "Contents")
+ (when org-noter-pdftools-insert-content-heading
+ (org-noter--insert-heading (1+ level) "Contents"))
(insert (car contents)))
(when (cdr contents)
- (org-noter--insert-heading (1+ level) "Comment")
+ (when org-noter-pdftools-insert-comment-heading
+ (org-noter--insert-heading (1+ level) "Comment"))
(insert (cdr contents)))))
(setq ast (org-noter--parse-root))
@@ -570,4 +637,71 @@ Only available with PDF Tools."
(outline-hide-subtree)
(org-show-children 2)))))
- (t (error "This command is only supported on PDF Tools.")))))
+ (t (error "This command is only supported on PDF Tools")))))
+
+(defun org-noter-pdftools-embed-org-note-to-pdf ()
+ "Embed a org subtree to its corresponding PDF annotation."
+ (interactive)
+ (org-noter--with-valid-session
+ (unless (equal (selected-window) (org-noter--get-notes-window))
+ (error "You should use this command in an org-noter note buffer"))
+ (let* ((org-id (org-id-get)))
+ (unless (and (string-match ".*\\(annot-.*-.*\\)" org-id)
+ org-noter-pdftools-use-org-id
+ org-noter-pdftools-use-pdftools-link-location)
+ (error "This can only be run on an org heading with a valid org-pdftools annotation ID.
+Please also make sure `org-noter-pdftools-use-org-id' and `org-noter-pdftools-use-pdftools-link-location' are enabled"))
+ (let* ((annot-id (match-string 1 org-id))
+ note)
+ (setq kr kill-ring)
+ (org-copy-subtree nil nil nil t)
+ (setq note (car kill-ring))
+ (setq kill-ring kr)
+ (with-selected-window
+ (org-noter--get-doc-window)
+ (let ((annot (pdf-annot-getannot (intern annot-id))))
+ (with-current-buffer (pdf-annot-edit-contents-noselect annot)
+ (insert note)
+ (pdf-annot-edit-contents-finalize t)))
+ (save-buffer))))))
+(defun org-noter-pdftools-embed-all-org-note-to-pdf ()
+ (interactive)
+ (org-noter--with-valid-session
+ (with-selected-window (org-noter--get-notes-window)
+ (save-excursion
+ (org-map-entries #'org-noter-pdftools-embed-org-note-to-pdf "ID={annot-}")))))
+(defun org-noter-pdftools-embed-org-buffer-to-pdf ()
+ "Embed the whole org-noter doc buffer to a PDF annotation."
+ (interactive)
+ (org-noter--with-valid-session
+ (let* ((note (with-selected-window (org-noter--get-notes-window)
+ (save-excursion
+ (buffer-substring-no-properties
+ (point-min) (point-max)))))
+ annot-id)
+ (with-selected-window
+ (org-noter--get-doc-window)
+ (save-excursion
+ (pdf-view-goto-page 1)
+ (setq annot-id
+ (pdf-annot-get-id
+ (let ((annot (ignore-errors (pdf-annot-at-position '(0 . 0)))))
+ (if annot
+ annot
+ (funcall-interactively
+ #'pdf-annot-add-text-annotation
+ '(0 . 0)
+ org-pdftools-free-pointer-icon
+ `((color . ,org-pdftools-free-pointer-color)
+ (opacity . ,org-pdftools-free-pointer-opacity))))))))
+ (with-selected-window
+ (org-noter--get-doc-window)
+ (let ((annot (pdf-annot-getannot annot-id)))
+ (with-current-buffer (pdf-annot-edit-contents-noselect annot)
+ (insert note)
+ (pdf-annot-edit-contents-finalize t)))
+ (save-buffer))))))
+
+(provide 'org-noter-pdftools)
+
+;;; org-noter-pdftools.el ends here
diff --git a/other/org-noter-nov-overlay.el b/other/org-noter-nov-overlay.el
new file mode 100644
index 0000000..f4cd0eb
--- /dev/null
+++ b/other/org-noter-nov-overlay.el
@@ -0,0 +1,116 @@
+;;; org-noter-nov-overlay.el --- Module to highlight text in nov-mode with notes -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Charlie Gordon
+
+;; Author: Charlie Gordon
+;; Keywords: multimedia
+
+;; 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,
+;; 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 .
+
+;;; Commentary:
+
+;; Highlight your precise notes in nov with org-noter-nov-overlay.el
+
+;;; Code:
+(require 'org-noter)
+(require 'nov)
+(require 'seq)
+
+(defcustom org-noter-nov-overlay-color-property "NOTER_OVERLAY"
+ "A property that specifies the overlay color for `org-noter-nov-make-ov'.")
+
+(defcustom org-noter-nov-overlay-default-color "SkyBlue"
+ "Name of the default background color of the overlay `org-noter-nov-make-ov' makes.
+
+Should be one of the element in `defined-colors'.")
+
+(defun org-noter-nov-make-overlays ()
+ (org-noter--with-selected-notes-window
+ (let* ((page (buffer-local-value 'nov-documents-index (org-noter--session-doc-buffer session)))
+ (regexp (org-re-property org-noter-property-note-location t nil
+ (format (rx "(" (* space) "%d" (+ space)
+ (+ digit) (+ space) "." (+ space)
+ (+ digit) (* space) ")")
+ page))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward regexp nil t)
+ (when-let ((location (org-entry-get nil org-noter-property-note-location nil t)))
+ (org-noter-nov-make-overlay-no-question)))))))
+
+(defun org-noter-nov-make-overlay ()
+ "TODO"
+ (org-noter--with-selected-notes-window
+ "No notes window exists"
+ (when (eq (org-noter--session-doc-mode session) 'nov-mode)
+ (let* ((location-property (org-entry-get nil org-noter-property-note-location nil t))
+ (location-cons (cdr (read location-property)))
+ (beg (car location-cons))
+ (end (cdr location-cons))
+ (ov-pair (list (make-overlay beg end (org-noter--session-doc-buffer session))))
+ (hl-color (or (org-entry-get nil org-noter-nov-overlay-color-property nil t)
+ (if org-noter-insert-note-no-questions
+ org-noter-nov-overlay-default-color
+ (read-color "Highlight color: "))))
+ (hl-color-alt (color-lighten-name hl-color 15))
+ (action-functions (list
+ #'org-noter-nov-overlay-sync-current-note
+ #'org-noter-nov-overlay-sync-current-page-or-chapter)))
+
+ (save-excursion
+ (org-back-to-heading t)
+ (re-search-forward org-heading-regexp nil t)
+ (push (make-overlay (match-beginning 1) (match-end 1)) ov-pair))
+
+ (dolist (ov ov-pair)
+ (overlay-put ov 'button ov)
+ (overlay-put ov 'category 'default-button)
+ (overlay-put ov 'face (list :background hl-color
+ :foreground (readable-foreground-color hl-color)))
+
+ (org-entry-put nil org-noter-nov-overlay-color-property hl-color)
+
+ (overlay-put ov 'mouse-face (list :background hl-color-alt
+ :foreground (readable-foreground-color hl-color-alt)))
+
+ (overlay-put ov 'action (pop action-functions)))))))
+
+(defun org-noter-nov-make-overlay-no-question ()
+ "Like `org-noter-nov-make-ov', but doesn't ask user to select the overlay color."
+ (org-noter--with-valid-session
+ (let ((org-noter-insert-note-no-questions t))
+ (org-noter-nov-make-overlay))))
+
+(defun org-noter-nov-overlay-sync-current-page-or-chapter (_overlay)
+ "A wrapper function for `org-noter-sync-current-page-or-chapter'
+used exclusively with overlays made with `org-noter-nov-make-overlay'
+
+This wrapper ignores the first argument passed to it and just call
+`org-noter-sync-current-page-or-chapter'."
+
+ (org-noter-sync-current-page-or-chapter))
+
+(defun org-noter-nov-overlay-sync-current-note (_overlay)
+ "A wrapper function for `org-noter-nov-overlay-sync-current-note'
+used exclusively with overlays made with `org-noter-nov-make-overlay'
+
+This wrapper ignores the first argument passed to it and just call
+`org-noter-nov-overlay-sync-current-note'."
+ (org-noter-sync-current-note))
+
+(add-hook 'nov-post-html-render-hook #'org-noter-nov-make-overlays)
+
+(provide 'org-noter-nov-overlay)
+;;; org-noter-nov-ov.el ends here
+
diff --git a/tests/MobyDick.pdf b/tests/MobyDick.pdf
new file mode 100644
index 0000000..7e2aa23
Binary files /dev/null and b/tests/MobyDick.pdf differ
diff --git a/tests/Notes.org b/tests/Notes.org
new file mode 100644
index 0000000..1726838
--- /dev/null
+++ b/tests/Notes.org
@@ -0,0 +1,998 @@
+#+COLUMNS: %25ITEM %NOTER_PAGE %NUM_COLUMNS
+#+STARTUP: overview
+
+* BuzzanitiG_EnricoFermi
+ :PROPERTIES:
+ :NOTER_DOCUMENT: _BuzzanitiG_EnricoFermi.pdf
+ :END:
+** Chapter 1: The last Galilean
+ :PROPERTIES:
+ :NOTER_PAGE: 17
+ :END:
+*** Fig 1.1 college entrance exam
+ :PROPERTIES:
+ :NOTER_PAGE: 20
+ :END:
+ First page of Fermi’s written exam to enter Scuola Normale Superiore
+
+ Second page of Fermi’s written exam to enter Scuola Normale Superiore
+** sin
+ :PROPERTIES:
+ :NOTER_PAGE: 45
+ :END:
+** last trip
+ :PROPERTIES:
+ :NOTER_PAGE: 59
+ :END:
+** Fig 2.4: cathode rays to nuclear atom
+ :PROPERTIES:
+ :NOTER_PAGE: 76
+ :END:
+** tx th
+ :PROPERTIES:
+ :NOTER_PAGE: 78
+ :END:
+** Fig 2.9 nuclear protophysics
+ :PROPERTIES:
+ :NOTER_PAGE: 91
+ :END:
+** beta decay
+ :PROPERTIES:
+ :NOTER_PAGE: 117
+ :END:
+** fig 3.3 Fermi on relativity
+ :PROPERTIES:
+ :NOTER_PAGE: 128
+ :END:
+** Fig 3.4 Fermi transitions to quantum mechanics
+ :PROPERTIES:
+ :NOTER_PAGE: 141
+ :END:
+** Chapter 4
+ :PROPERTIES:
+ :NOTER_PAGE: 172
+ :END:
+** 4.1 Nuclei and particle accelerators
+ :PROPERTIES:
+ :NOTER_PAGE: (172 0.7057971014492753 . 0.10417582417582417)
+ :END:
+** 4.3 The “birth song”
+ :PROPERTIES:
+ :NOTER_PAGE: (177 0.4181159420289855 . 0.09758241758241758)
+ :END:
+Nowadays we know that cosmic rays are mainly protons (about 85%)
+
+#+BEGIN_QUOTE
+This whole work constitutes, then, very powerful evidence that the sort of creative, or atom-
+building processes discussed above, are continually going on all about us, possibly also
+even on the earth, and that each such event is broadcast through the heavens in the form of
+the appropriate cosmic ray.14
+#+END_QUOTE
+** Fig. 4.6
+ :PROPERTIES:
+ :NOTER_PAGE: (185 0.4623188405797102 . 0.09978021978021978)
+ :END:
+ Neutron physics
+** Letter from Rutherford
+ :PROPERTIES:
+ :NOTER_PAGE: (217 0.2710144927536232 . 0.13494505494505496)
+ :END:
+#+BEGIN_QUOTE
+Dear Fermi,
+I have to thank you for your kindness in sending me an account of your recent experiments
+in causing temporary radioactivity in a number of elements by means of neutrons. Your
+results are of great interest, and no doubt later we shall be able to obtain more information
+as to the actual mechanism of such transformations. It is by no means clear that in all
+cases the process is as simple as appears to be the case in the observations of the Joliot.
+I congratulate you on your successful escape from the sphere of theoretical physics! You
+seem to have struck a good line to start with. You may be interested to hear that Professor
+Dirac also is doing some experiments. This seems to be a good augury for the future of
+theoretical physics!
+Congratulations and best wishes.
+Yours sincerely, Rutherford11
+#+END_QUOTE
+** Epilogue
+ :PROPERTIES:
+ :NOTER_PAGE: (273 0.09202898550724638 . 0.09868131868131867)
+ :END:
+** Chicago days
+ :PROPERTIES:
+ :NOTER_PAGE: 311
+ :END:
+TD Lee's stories about Fermi and Chicago in the late 40s.
+
+Nevertheless, since Fermi was not scheduled
+to give any courses that quarter, I did register for quantum mechanics with Teller,
+electromagnetic theory with Zachariasen, and, later, statistical mechanics with both
+Mayers. By attending those classes I felt I was betraying the secret that I was not
+an exceptional student. However, that feeling was soon dissipated by my observing
+that there were many other students in these classes.
+** B.6 T. D. Lee. Reminiscence of Chicago days
+ :PROPERTIES:
+ :NOTER_PAGE: (311 0.10144927536231885 . 0.10637362637362636)
+ :END:
+ square dancing!
+** slide rule
+ :PROPERTIES:
+ :NOTER_PAGE: 312
+ :END:
+radiative transfer equations
+** Appendix C Background material
+ :PROPERTIES:
+ :NOTER_PAGE: 314
+ :END:
+** C.1 Newtonian mechanics; inertial and gravitational mass
+ :PROPERTIES:
+ :NOTER_PAGE: (314 0.3521739130434783 . 0.10747252747252746)
+ :END:
+
+** The identity of the inertial and gravitational mass is a remarkable fact
+ :PROPERTIES:
+ :NOTER_PAGE: (314 0.7768115942028986 . 0.12725274725274727)
+ :END:
+
+** C.2 Curved space: ... Flatland and Spheriland
+ :PROPERTIES:
+ :NOTER_PAGE: (315 0.09927536231884058 . 0.10307692307692308)
+ :END:
+C.2 Curved space: the strange worlds of Flatland
+and Spheriland
+
+Rev. Edwin A. Abbott
+
+They are thinking beings with a rigid social structure; the circles are at the
+vertex of the social pyramid; they are the high priests, who control the power. The
+aristocracy is formed by the regular polygons, the midd
+
+** Fig C.2
+ :PROPERTIES:
+ :NOTER_PAGE: (316 0.16014492753623188 . 0.11428571428571428)
+ :END:
+positive curvature
+
+** Fig. C.3 neg curv
+ :PROPERTIES:
+ :NOTER_PAGE: (316 0.39420289855072466 . 0.2813186813186813)
+ :END:
+sum of the interior angles of a triangle is less than 180
+** C.3 ˛ particle scattering
+ :PROPERTIES:
+ :NOTER_PAGE: (316 0.7007246376811594 . 0.10307692307692308)
+ :END:
+#+BEGIN_QUOTE
+The passage of particles through matter has been the first tool to investigate the
+atomic structure. ˛ particles (having twice the charge of the electron, and a mass four
+times bigger than the hydrogen atom mass) are emitted by radioactive substances at
+a very high speed (about 107 m/s). Due to their high speed they can travel in air for
+several centimeters and cross thick layers of several substances, such as gold. Fermi
+in his textbook on atomic physics explained w
+#+END_QUOTE
+
+Let us now suppose that a thin beam of \alpha particles o
+
+** Let us now suppose that a thin beam of ˛ particles o
+ :PROPERTIES:
+ :NOTER_PAGE: (316 0.8775362318840579 . 0.13054945054945055)
+ :END:
+
+** C.4 Planck’s constant and the birth of the wave-particle duality
+ :PROPERTIES:
+ :NOTER_PAGE: (317 0.7130434782608696 . 0.10197802197802197)
+ :END:
+
+** C.5 The electron spin and the exclusion principle
+ :PROPERTIES:
+ :NOTER_PAGE: (320 0.6094202898550725 . 0.10307692307692308)
+ :END:
+The failure to provide an explanation of the anomalous Zeeman effect was certainly
+the main reason why at the beginning of the 20s a fourth quantum number was
+introduced. Uhlenbeck and Goudsmit’s idea was that the difficulties lay in some
+unknown structural property of the electron. In particular they made the hypothesis
+that the electron rotates around its axis, thus having an angular momentum, and
+therefore a magnetic momentum. The empirical evidence of the doubling of the
+spectral line of the alkaline metals implied that this intrinsic angular momentum of
+the electron (spin) only can have two directions in space with respect to a given
+direction. An easy calculation shows that the absolute value of the spin must be 1/2
+
+* MahajanS_Art of Insight (navigation timing)
+ :PROPERTIES:
+ :NOTER_DOCUMENT: MahajanS_Art of Insight.pdf
+ :END:
+** Skeleton
+ 8th and 9th notes are precise (pg . v)
+*** Preface
+ :PROPERTIES:
+ :NOTER_PAGE: 15
+ :END:
+ #+begin_src elisp
+ (let (ii jj
+ (note-steps 17)
+ (repeats 10))
+ (measure-time
+ (other-window 1)
+ (dotimes (jj repeats)
+ (dotimes (ii note-steps) (org-noter-sync-next-page-or-chapter))
+ (dotimes (ii note-steps) (org-noter-sync-prev-page-or-chapter)))
+ (other-window 1)))
+ #+end_src
+
+ #+RESULTS:
+ : 4.525487
+
+ org-babel is slow on subsequent runs after B9, but direct execution with C-x
+ C-e does not suffer the same performance degradation.
+
+**** summary of results
+ (PM) 56a45e0: 4.45s (17x10) slower 2nd time in org babel, but consistent w/ C-xC-e
+ (PM) 7d94dc2: 3.279694, 9.246695 (2 note-steps x 1 repeats)
+ (B9) f74263f: 5.030582, 13.833845 (3x1)
+ (B5) 49bc6ee: 11.525995 (7 note-steps x 1 repeat)
+ (A3) a83a2eb: 3.046663 ( 7 note-steps x 24 repeats), nav broken
+ (GS) 9ead81d: 3.051076 (17 note-steps x 10 repeats)
+
+**** raw results
+ (PM) 56a45e0: 4.45s (17x10) slower 2nd time in org babel, but consistent w/ C-xC-e
+ (PM) cd3c86c: 22.911387 (7 x 1) 2nd
+ (PM) cd3c86c: 4.101886, (7 x 24) 1st
+ (PM) 7d94dc2: 3.279694, 9.246695 (2 note-steps x 1 repeats)
+ (D1) a494169: 9.266794 (2 note-steps x 1 repeats)
+ (M2) b30cbaa: 11.612586, 32.265847 (7 note-steps x 1 repeat)
+
+ (B6) b9ddcbd: 32.272890 (7 note-steps x 1 repeat), 2nd time
+ (B6) b9ddcbd: 11.630274 (7 note-steps x 1 repeat), 1st time
+
+ (B7) 5002732: 11.78...., 32.263172 (7 note-steps x 1 repeat) 1st,2nd
+ (B8) e417890: 11.627328, 32....... (7 note-steps x 1 repeat) 1st,2nd
+
+ (B9) f74263f: 5.030582 , 13.833845 (3x1)
+ (B1) e8d3fc1: 11.571044 (7 note-steps x 1 repeat)
+ (B2) 52a14db: 11.607116 (7 note-steps x 1 repeat)
+ (B4) bec9767: 11.557839 (7 note-steps x 1 repeat)
+ (B5) 49bc6ee: 11.525995 (7 note-steps x 1 repeat)
+ (B3) f38f313: 3.083761 ( 7 note-steps x 24 repeats)
+ (A6) ced2751: 3.049991 ( 7 note-steps x 24 repeats), nav broken
+ (A3) a83a2eb: 3.046663 ( 7 note-steps x 24 repeats), nav broken
+ (A2) 3924fd8: 3.013850 (17 note-steps x 10 repeats), nav OK
+ (C1) 924dc55: 2.951571 (17 note-steps x 10 repeats)
+ (GS) 9ead81d: 3.051076 (17 note-steps x 10 repeats)
+
+
+ #+begin_src elisp
+ (let (ii jj
+ (note-steps 2)
+ (repeats 1))
+ (measure-time
+ (other-window 1)
+ (dotimes (jj repeats)
+ (dotimes (ii note-steps) (org-noter-sync-next-note))
+ (dotimes (ii note-steps) (org-noter-sync-prev-note)))
+ (other-window 1)))
+ #+end_src
+
+ #+RESULTS:
+
+ (PM) 7d94dc2: 9.246695 (2 note-steps x 1 repeats)
+ (GS) 9ead81d: 3.051076 (17 note-steps x 10 repeats)
+
+*** Values for Backs of Envelopes
+ :PROPERTIES:
+ :NOTER_PAGE: 19
+ :COLUMN_EDGES: (0.27281191806331473 0.5633147113594041 0.6675977653631285 1)
+ :END:
+(car (read-from-string (org-entry-get nil "COLUMN_EDGES" t)))
+
+**** R
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.2954380883417813 . 0.15597765363128493)
+ :END:
+gas const
+
+**** Notes for page xvii V: 75% H: 16%
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.7509051412020276 . 0.15597765363128493)
+ :END:
+**** "m" of Boltzmann with W
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.37880184331797234 . 0.3981042654028436)
+ :END:
+ -0.0075 with W
+ -0.018 with P
+ (* 7.5 (/ 211.0 (- 211 134.25)))
+ org-noter--arrow-location
+**** s in sun
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.4554670528602462 . 0.3547486033519553)
+ :END:
+**** M of moon
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.4677769732078204 . 0.4590316573556797)
+ :END:
+**** Moon or Sun
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.4742939898624185 . 0.46834264432029793)
+ :HIGHLIGHT: #s(pdf-highlight 19 ((0.46834264432029793 0.4742939898624185 0.5735567970204841 0.47863866763215057)))
+ :END:
+**** human basal metabolic rate
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.6748732802317161 . 0.3063314711359404)
+ :END:
+
+**** notes for page xvii V: 22% H: 60%
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.22157856625633598 . 0.6029050279329609)
+ :END:
+i
+**** Notes for page xvii V: 32% H: 67%
+ :PROPERTIES:
+ :NOTER_PAGE: (19 0.32005792903692976 . 0.6699441340782123)
+ :END:
+*** Part I: Organizing Complexity
+ :PROPERTIES:
+ :NOTER_PAGE: 21
+ :END:
+**** 1. Divide and conquer
+ :PROPERTIES:
+ :NOTER_PAGE: 23
+ :END:
+**** 2. Abstraction
+ :PROPERTIES:
+ :NOTER_PAGE: 47
+ :END:
+*** Part II: Discarding Complexity Without Losing Information
+ :PROPERTIES:
+ :NOTER_PAGE: 75
+ :END:
+**** 3. Symmetry and Conservation
+ :PROPERTIES:
+ :NOTER_PAGE: 77
+ :END:
+**** 4. Proportional Reasoning
+ :PROPERTIES:
+ :NOTER_PAGE: 123
+ :END:
+**** 4.1 Population scaling
+ :PROPERTIES:
+ :NOTER_PAGE: (123 . 0.6335988414192614)
+ :END:
+**** 4.2 Finding scaling exponents
+ :PROPERTIES:
+ :NOTER_PAGE: (125 . 0.12599565532223025)
+ :END:
+
+**** 5. Dimensions
+ :PROPERTIES:
+ :NOTER_PAGE: 157
+ :END:
+*** Part III: Discarding Complexity with Loss of Information
+ :PROPERTIES:
+ :NOTER_PAGE: 217
+ :END:
+**** 6. Lumping
+ :PROPERTIES:
+ :NOTER_PAGE: 219
+ :END:
+**** 7. Probabilistic Reasoning
+ :PROPERTIES:
+ :NOTER_PAGE: 255
+ :END:
+**** 8. Easy Cases
+ :PROPERTIES:
+ :NOTER_PAGE: 299
+ :END:
+***** Wave dispersion diagram
+ :PROPERTIES:
+ :NOTER_PAGE: (330 0.2831281679942071 . 0.10383612662942271)
+ :END:
+
+**** 9. Spring Models
+ :PROPERTIES:
+ :NOTER_PAGE: 337
+ :END:
+*** Bon Voyage: Long-Lasting Learning
+ :PROPERTIES:
+ :NOTER_PAGE: 377
+ :END:
+*** Bibliography
+ :PROPERTIES:
+ :NOTER_PAGE: 379
+ :END:
+*** Index
+ :PROPERTIES:
+ :NOTER_PAGE: 383
+ :END:
+* MobyDick
+ :PROPERTIES:
+ :NOTER_DOCUMENT: MobyDick.pdf
+ :NOTER_PAGE: 171
+ :END:
+** Skeleton
+ To time this code, you need the measure-time macro.
+ #+begin_src elisp
+ ;; http://lists.gnu.org/archive/html/help-gnu-emacs/2008-06/msg00087.html
+ (defmacro measure-time (&rest body)
+ "Measure the time it takes to evaluate BODY."
+ `(let ((time (current-time)))
+ ,@body
+ (message "%.06f" (float-time (time-since time)))))
+
+ (defmacro measure-time-sexp (&rest body)
+ "Measure the time it takes to evaluate BODY.
+ Returns the value of BODY, so it can be used to time any elisp
+ sexp."
+ `(let* ((time (current-time))
+ (retval ,@body))
+ (message "%.06f" (float-time (time-since time)))
+ retval))
+ #+end_src
+*** Title page
+ :PROPERTIES:
+ :NOTER_PAGE: 1
+ :END:
+
+ #+begin_src elisp
+ (let (ii jj
+ (note-steps 138)
+ (repeats 1))
+ (measure-time
+ (other-window 1)
+ (dotimes (jj repeats)
+ (dotimes (ii note-steps) (org-noter-sync-next-page-or-chapter))
+ (dotimes (ii note-steps) (org-noter-sync-prev-page-or-chapter)))
+ (other-window -1)))
+ #+end_src
+
+ #+RESULTS:
+ : 10.116560
+
+*** ETYMOLOGY
+ :PROPERTIES:
+ :NOTER_PAGE: (11 . 0.123031)
+ :END:
+*** EXTRACTS
+ :PROPERTIES:
+ :NOTER_PAGE: (13 . 0.123031)
+ :END:
+*** LOOMINGS
+ :PROPERTIES:
+ :NOTER_PAGE: (25 . 0.123031)
+ :END:
+*** THE CARPET-BAG
+ :PROPERTIES:
+ :NOTER_PAGE: (31 . 0.123031)
+ :END:
+*** THE SPOUTER-INN
+ :PROPERTIES:
+ :NOTER_PAGE: (37 . 0.123031)
+ :END:
+*** THE COUNTERPANE
+ :PROPERTIES:
+ :NOTER_PAGE: (53 . 0.123031)
+ :END:
+*** BREAKFAST
+ :PROPERTIES:
+ :NOTER_PAGE: (59 . 0.123031)
+ :END:
+*** THE STREET
+ :PROPERTIES:
+ :NOTER_PAGE: (63 . 0.123031)
+ :END:
+*** THE CHAPEL
+ :PROPERTIES:
+ :NOTER_PAGE: (67 . 0.123031)
+ :END:
+*** THE PULPIT
+ :PROPERTIES:
+ :NOTER_PAGE: (71 . 0.123031)
+ :END:
+*** THE SERMON
+ :PROPERTIES:
+ :NOTER_PAGE: (75 . 0.123031)
+ :END:
+*** A BOSOM FRIEND
+ :PROPERTIES:
+ :NOTER_PAGE: (85 . 0.123031)
+ :END:
+*** NIGHTGOWN
+ :PROPERTIES:
+ :NOTER_PAGE: (91 . 0.123031)
+ :END:
+*** BIOGRAPHICAL
+ :PROPERTIES:
+ :NOTER_PAGE: (95 . 0.123031)
+ :END:
+*** WHEELBARROW
+ :PROPERTIES:
+ :NOTER_PAGE: (99 . 0.123031)
+ :END:
+*** NANTUCKET
+ :PROPERTIES:
+ :NOTER_PAGE: (105 . 0.123031)
+ :END:
+*** CHOWDER
+ :PROPERTIES:
+ :NOTER_PAGE: (109 . 0.123031)
+ :END:
+*** THE SHIP
+ :PROPERTIES:
+ :NOTER_PAGE: (113 . 0.123031)
+ :END:
+*** THE RAMADAN
+ :PROPERTIES:
+ :NOTER_PAGE: (129 . 0.123031)
+ :END:
+*** HIS MARK
+ :PROPERTIES:
+ :NOTER_PAGE: (137 . 0.123031)
+ :END:
+*** THE PROPHET
+ :PROPERTIES:
+ :NOTER_PAGE: (143 . 0.123031)
+ :END:
+*** ALL ASTIR
+ :PROPERTIES:
+ :NOTER_PAGE: (147 . 0.123031)
+ :END:
+*** GOING ABOARD
+ :PROPERTIES:
+ :NOTER_PAGE: (151 . 0.123031)
+ :END:
+*** MERRY CHRISTMAS
+ :PROPERTIES:
+ :NOTER_PAGE: (155 . 0.123031)
+ :END:
+*** THE LEE SHORE
+ :PROPERTIES:
+ :NOTER_PAGE: (161 . 0.123031)
+ :END:
+*** THE ADVOCATE
+ :PROPERTIES:
+ :NOTER_PAGE: (163 . 0.123031)
+ :END:
+*** POSTSCRIPT
+ :PROPERTIES:
+ :NOTER_PAGE: (169 . 0.123031)
+ :END:
+*** KNIGHTS AND SQUIRES
+ :PROPERTIES:
+ :NOTER_PAGE: (171 . 0.123031)
+ :END:
+*** KNIGHTS AND SQUIRES
+ :PROPERTIES:
+ :NOTER_PAGE: (175 . 0.123031)
+ :END:
+*** AHAB
+ :PROPERTIES:
+ :NOTER_PAGE: (181 . 0.123031)
+ :END:
+*** ENTER AHAB; TO HIM, STUBB
+ :PROPERTIES:
+ :NOTER_PAGE: (185 . 0.123031)
+ :END:
+*** THE PIPE
+ :PROPERTIES:
+ :NOTER_PAGE: (189 . 0.123031)
+ :END:
+*** QUEEN MAB
+ :PROPERTIES:
+ :NOTER_PAGE: (191 . 0.123031)
+ :END:
+*** CETOLOGY
+ :PROPERTIES:
+ :NOTER_PAGE: (195 . 0.123031)
+ :END:
+*** THE SPECKSNYDER
+ :PROPERTIES:
+ :NOTER_PAGE: (209 . 0.123031)
+ :END:
+*** THE CABIN-TABLE
+ :PROPERTIES:
+ :NOTER_PAGE: (213 . 0.123031)
+ :END:
+*** THE MAST-HEAD
+ :PROPERTIES:
+ :NOTER_PAGE: (219 . 0.123031)
+ :END:
+*** THE QUARTER-DECK
+ :PROPERTIES:
+ :NOTER_PAGE: (227 . 0.123031)
+ :END:
+*** SUNSET
+ :PROPERTIES:
+ :NOTER_PAGE: (235 . 0.123031)
+ :END:
+*** DUSK
+ :PROPERTIES:
+ :NOTER_PAGE: (237 . 0.123031)
+ :END:
+*** FIRST NIGHT-WATCH FORE-TOP
+ :PROPERTIES:
+ :NOTER_PAGE: (239 . 0.123031)
+ :END:
+*** MIDNIGHT, FORECASTLE
+ :PROPERTIES:
+ :NOTER_PAGE: (241 . 0.123031)
+ :END:
+*** MOBY DICK
+ :PROPERTIES:
+ :NOTER_PAGE: (247 . 0.123031)
+ :END:
+*** THE WHITENESS OF THE WHALE
+ :PROPERTIES:
+ :NOTER_PAGE: (259 . 0.123031)
+ :END:
+*** HARK!
+ :PROPERTIES:
+ :NOTER_PAGE: (269 . 0.123031)
+ :END:
+*** THE CHART
+ :PROPERTIES:
+ :NOTER_PAGE: (271 . 0.123031)
+ :END:
+*** THE AFFIDAVIT
+ :PROPERTIES:
+ :NOTER_PAGE: (277 . 0.123031)
+ :END:
+*** SURMISES
+ :PROPERTIES:
+ :NOTER_PAGE: (287 . 0.123031)
+ :END:
+*** THE MAT-MAKER
+ :PROPERTIES:
+ :NOTER_PAGE: (291 . 0.123031)
+ :END:
+*** THE FIRST LOWERING
+ :PROPERTIES:
+ :NOTER_PAGE: (295 . 0.123031)
+ :END:
+*** THE HYENA
+ :PROPERTIES:
+ :NOTER_PAGE: (307 . 0.123031)
+ :END:
+*** AHAB’S BOAT AND CREW. FEDALLAH
+ :PROPERTIES:
+ :NOTER_PAGE: (311 . 0.123031)
+ :END:
+*** THE SPIRIT-SPOUT
+ :PROPERTIES:
+ :NOTER_PAGE: (315 . 0.123031)
+ :END:
+*** THE ALBATROSS
+ :PROPERTIES:
+ :NOTER_PAGE: (321 . 0.123031)
+ :END:
+*** THE GAM
+ :PROPERTIES:
+ :NOTER_PAGE: (325 . 0.123031)
+ :END:
+*** THE TOWN-HO’S STORY
+ :PROPERTIES:
+ :NOTER_PAGE: (331 . 0.123031)
+ :END:
+*** OF THE MONSTROUS PICTURES OF WHALES
+ :PROPERTIES:
+ :NOTER_PAGE: (353 . 0.123031)
+ :END:
+*** OF THE LESS ERRONEOUS PICTURES OF WHALES, AND THE TRUE PICTURES OF WHALING SCENES
+ :PROPERTIES:
+ :NOTER_PAGE: (359 . 0.123031)
+ :END:
+*** OF WHALES IN PAINT; IN TEETH; IN WOOD; IN SHEET-IRON; IN STONE; IN MOUNTAINS; IN STARS
+ :PROPERTIES:
+ :NOTER_PAGE: (365 . 0.123031)
+ :END:
+*** BRIT
+ :PROPERTIES:
+ :NOTER_PAGE: (369 . 0.123031)
+ :END:
+*** SQUID
+ :PROPERTIES:
+ :NOTER_PAGE: (373 . 0.123031)
+ :END:
+*** THE LINE
+ :PROPERTIES:
+ :NOTER_PAGE: (377 . 0.123031)
+ :END:
+*** STUBB KILLS A WHALE
+ :PROPERTIES:
+ :NOTER_PAGE: (381 . 0.123031)
+ :END:
+*** THE DART
+ :PROPERTIES:
+ :NOTER_PAGE: (387 . 0.123031)
+ :END:
+*** THE CROTCH
+ :PROPERTIES:
+ :NOTER_PAGE: (389 . 0.123031)
+ :END:
+*** STUBB’S SUPPER
+ :PROPERTIES:
+ :NOTER_PAGE: (391 . 0.123031)
+ :END:
+*** THE WHALE AS A DISH
+ :PROPERTIES:
+ :NOTER_PAGE: (401 . 0.123031)
+ :END:
+*** THE SHARK MASSACRE
+ :PROPERTIES:
+ :NOTER_PAGE: (405 . 0.123031)
+ :END:
+*** CUTTING IN
+ :PROPERTIES:
+ :NOTER_PAGE: (409 . 0.123031)
+ :END:
+*** THE BLANKET
+ :PROPERTIES:
+ :NOTER_PAGE: (413 . 0.123031)
+ :END:
+*** THE FUNERAL
+ :PROPERTIES:
+ :NOTER_PAGE: (417 . 0.123031)
+ :END:
+*** THE SPHYNX
+ :PROPERTIES:
+ :NOTER_PAGE: (419 . 0.123031)
+ :END:
+*** THE JEROBOAM’S STORY
+ :PROPERTIES:
+ :NOTER_PAGE: (423 . 0.123031)
+ :END:
+*** THE MONKEY-ROPE
+ :PROPERTIES:
+ :NOTER_PAGE: (431 . 0.123031)
+ :END:
+*** STUBB AND FLASK KILL A RIGHT WHALE; AND THEN HAVE A TALK OVER HIM
+ :PROPERTIES:
+ :NOTER_PAGE: (437 . 0.123031)
+ :END:
+*** THE SPERM WHALE’S HEAD – CONTRASTED VIEW
+ :PROPERTIES:
+ :NOTER_PAGE: (443 . 0.123031)
+ :END:
+*** THE RIGHT WHALE’S HEAD – CONTRASTED VIEW
+ :PROPERTIES:
+ :NOTER_PAGE: (449 . 0.123031)
+ :END:
+*** THE BATTERING-RAM
+ :PROPERTIES:
+ :NOTER_PAGE: (453 . 0.123031)
+ :END:
+*** THE GREAT HEIDELBURGH TUN
+ :PROPERTIES:
+ :NOTER_PAGE: (457 . 0.123031)
+ :END:
+*** CISTERN AND BUCKETS
+ :PROPERTIES:
+ :NOTER_PAGE: (461 . 0.123031)
+ :END:
+*** THE PRAIRE
+ :PROPERTIES:
+ :NOTER_PAGE: (467 . 0.123031)
+ :END:
+*** THE NUT
+ :PROPERTIES:
+ :NOTER_PAGE: (471 . 0.123031)
+ :END:
+*** THE PEQUOD MEETS THE VIRGIN
+ :PROPERTIES:
+ :NOTER_PAGE: (475 . 0.123031)
+ :END:
+*** THE HONOR AND GLORY OF WHALING
+ :PROPERTIES:
+ :NOTER_PAGE: (487 . 0.123031)
+ :END:
+*** JONAH HISTORICALLY REGARDED
+ :PROPERTIES:
+ :NOTER_PAGE: (491 . 0.123031)
+ :END:
+*** PITCHPOLING
+ :PROPERTIES:
+ :NOTER_PAGE: (495 . 0.123031)
+ :END:
+*** THE FOUNTAIN
+ :PROPERTIES:
+ :NOTER_PAGE: (499 . 0.123031)
+ :END:
+*** THE TAIL
+ :PROPERTIES:
+ :NOTER_PAGE: (505 . 0.123031)
+ :END:
+*** THE GRAND ARMADA
+ :PROPERTIES:
+ :NOTER_PAGE: (511 . 0.123031)
+ :END:
+*** SCHOOLS AND SCHOOLMASTERS
+ :PROPERTIES:
+ :NOTER_PAGE: (525 . 0.123031)
+ :END:
+*** FAST-FISH AND LOOSE-FISH
+ :PROPERTIES:
+ :NOTER_PAGE: (529 . 0.123031)
+ :END:
+*** HEADS OR TAILS
+ :PROPERTIES:
+ :NOTER_PAGE: (535 . 0.123031)
+ :END:
+*** THE PEQUOD MEETS THE ROSE-BUD
+ :PROPERTIES:
+ :NOTER_PAGE: (539 . 0.123031)
+ :END:
+*** AMBERGRIS
+ :PROPERTIES:
+ :NOTER_PAGE: (547 . 0.123031)
+ :END:
+*** THE CASTAWAY
+ :PROPERTIES:
+ :NOTER_PAGE: (551 . 0.123031)
+ :END:
+*** A SQUEEZE OF THE HAND
+ :PROPERTIES:
+ :NOTER_PAGE: (557 . 0.123031)
+ :END:
+*** THE CASSOCK
+ :PROPERTIES:
+ :NOTER_PAGE: (561 . 0.123031)
+ :END:
+*** THE TRY-WORKS
+ :PROPERTIES:
+ :NOTER_PAGE: (563 . 0.123031)
+ :END:
+*** THE LAMP
+ :PROPERTIES:
+ :NOTER_PAGE: (569 . 0.123031)
+ :END:
+*** STOWING DOWN AND CLEARING UP
+ :PROPERTIES:
+ :NOTER_PAGE: (571 . 0.123031)
+ :END:
+*** THE DOUBLOON
+ :PROPERTIES:
+ :NOTER_PAGE: (575 . 0.123031)
+ :END:
+*** LEG AND ARM. THE PEQUOD, OF NANTUCKET, MEETS THE SAMUEL ENDERBY, OF LONDON
+ :PROPERTIES:
+ :NOTER_PAGE: (583 . 0.123031)
+ :END:
+*** THE DECANTER
+ :PROPERTIES:
+ :NOTER_PAGE: (591 . 0.123031)
+ :END:
+*** A BOWER IN THE ARSACIDES
+ :PROPERTIES:
+ :NOTER_PAGE: (597 . 0.123031)
+ :END:
+*** MEASUREMENT OF THE WHALE’S SKELETON
+ :PROPERTIES:
+ :NOTER_PAGE: (603 . 0.123031)
+ :END:
+*** THE FOSSIL WHALE
+ :PROPERTIES:
+ :NOTER_PAGE: (607 . 0.123031)
+ :END:
+*** DOES THE WHALE’S MAGNITUDE DIMINISH? – WILL HE PERISH?
+ :PROPERTIES:
+ :NOTER_PAGE: (613 . 0.123031)
+ :END:
+*** AHAB’S LEG
+ :PROPERTIES:
+ :NOTER_PAGE: (619 . 0.123031)
+ :END:
+*** THE CARPENTER
+ :PROPERTIES:
+ :NOTER_PAGE: (623 . 0.123031)
+ :END:
+*** AHAB AND THE CARPENTER
+ :PROPERTIES:
+ :NOTER_PAGE: (627 . 0.123031)
+ :END:
+*** AHAB AND STARBUCK IN THE CABIN
+ :PROPERTIES:
+ :NOTER_PAGE: (633 . 0.123031)
+ :END:
+*** QUEEQUEG IN HIS COFFIN
+ :PROPERTIES:
+ :NOTER_PAGE: (637 . 0.123031)
+ :END:
+*** THE PACIFIC
+ :PROPERTIES:
+ :NOTER_PAGE: (645 . 0.123031)
+ :END:
+*** THE BLACKSMITH
+ :PROPERTIES:
+ :NOTER_PAGE: (647 . 0.123031)
+ :END:
+*** THE FORGE
+ :PROPERTIES:
+ :NOTER_PAGE: (651 . 0.123031)
+ :END:
+*** THE GILDER
+ :PROPERTIES:
+ :NOTER_PAGE: (655 . 0.123031)
+ :END:
+*** THE PEQUOD MEETS THE BACHELOR
+ :PROPERTIES:
+ :NOTER_PAGE: (659 . 0.123031)
+ :END:
+*** THE DYING WHALE
+ :PROPERTIES:
+ :NOTER_PAGE: (663 . 0.123031)
+ :END:
+*** THE WHALE WATCH
+ :PROPERTIES:
+ :NOTER_PAGE: (665 . 0.123031)
+ :END:
+*** THE QUADRANT
+ :PROPERTIES:
+ :NOTER_PAGE: (667 . 0.123031)
+ :END:
+*** THE CANDLES
+ :PROPERTIES:
+ :NOTER_PAGE: (671 . 0.123031)
+ :END:
+*** THE DECK TOWARDS THE END OF THE FIRST NIGHT WATCH
+ :PROPERTIES:
+ :NOTER_PAGE: (679 . 0.123031)
+ :END:
+*** MIDNIGHT – THE FORECASTLE BULWARKS
+ :PROPERTIES:
+ :NOTER_PAGE: (681 . 0.123031)
+ :END:
+*** MIDNIGHT ALOFT – THUNDER AND LIGHTNING
+ :PROPERTIES:
+ :NOTER_PAGE: (685 . 0.123031)
+ :END:
+*** THE MUSKET
+ :PROPERTIES:
+ :NOTER_PAGE: (687 . 0.123031)
+ :END:
+*** THE NEEDLE
+ :PROPERTIES:
+ :NOTER_PAGE: (691 . 0.123031)
+ :END:
+*** THE LOG AND LINE
+ :PROPERTIES:
+ :NOTER_PAGE: (695 . 0.123031)
+ :END:
+*** THE LIFE-BUOY
+ :PROPERTIES:
+ :NOTER_PAGE: (699 . 0.123031)
+ :END:
+*** THE DECK
+ :PROPERTIES:
+ :NOTER_PAGE: (703 . 0.123031)
+ :END:
+*** THE PEQUOD MEETS THE RACHEL
+ :PROPERTIES:
+ :NOTER_PAGE: (707 . 0.123031)
+ :END:
+*** THE CABIN
+ :PROPERTIES:
+ :NOTER_PAGE: (713 . 0.123031)
+ :END:
+*** THE HAT
+ :PROPERTIES:
+ :NOTER_PAGE: (715 . 0.123031)
+ :END:
+*** THE PEQUOD MEETS THE DELIGHT
+ :PROPERTIES:
+ :NOTER_PAGE: (721 . 0.123031)
+ :END:
+*** THE SYMPHONY
+ :PROPERTIES:
+ :NOTER_PAGE: (723 . 0.123031)
+ :END:
+*** THE CHASE – FIRST DAY
+ :PROPERTIES:
+ :NOTER_PAGE: (729 . 0.123031)
+ :END:
+*** THE CHASE – SECOND DAY
+ :PROPERTIES:
+ :NOTER_PAGE: (739 . 0.123031)
+ :END:
+*** THE CHASE – THIRD DAY
+ :PROPERTIES:
+ :NOTER_PAGE: (749 . 0.123031)
+ :END:
+*** EPILOGUE.
+ :PROPERTIES:
+ :NOTER_PAGE: (761 . 0.123031)
+ :END:
diff --git a/tests/org-noter-core-tests.el b/tests/org-noter-core-tests.el
new file mode 100644
index 0000000..147c53a
--- /dev/null
+++ b/tests/org-noter-core-tests.el
@@ -0,0 +1,217 @@
+(add-to-list 'load-path "modules")
+(require 'with-simulated-input)
+(require 'org-noter-test-utils)
+
+
+(describe "org-noter-core"
+ (before-each
+ (create-org-noter-test-session)
+ )
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (describe "note taking functionality"
+ ;; checking to make sure that `with-mock-contents` works fine.
+ (it "can parse a note file ast that is not empty"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda () (let ((mock-ast (org-noter--parse-root)))
+ (expect mock-ast :not :to-be nil)))))
+
+ ;; basic note should insert a default heading
+ (it "can take a basic note"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (let ((org-noter-insert-note-no-questions t))
+ (org-noter-insert-note nil "NEW NOTE"))
+ (expect 'org-noter-test-get-selected-text :to-have-been-called)
+ (expect (string-match "Notes for page" (buffer-string)) :not :to-be nil))))
+
+ ;; enter a heading when taking a precise note; expect the heading to be there.
+ (it "can take a precise note"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "precise SPC note RET"
+ (org-noter-insert-precise-note))
+ (expect (string-match "precise note" (buffer-string)) :not :to-be nil))))
+
+ ;; there should be precise data in the note properties when entering a precise note
+ (it "precise note has precise data"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "precise SPC note RET"
+ (org-noter-insert-precise-note))
+ (expect (string-match "NOTER_PAGE:" (buffer-string)) :not :to-be nil)
+ (expect (string-match "BEGIN_QUOTE" (buffer-string)) :not :to-be nil)
+ (expect 'org-noter-core-test-get-precise-info :to-have-been-called)
+ )))
+
+ ;; highlight code should be called when a precise note is entered
+ (it "precise note calls the highlight hook"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "precise SPC note RET"
+ (org-noter-insert-precise-note))
+ (expect 'org-noter-core-test-add-highlight :to-have-been-called))))
+
+ ;; hit C-g when entering a note; expect no highlight
+ (it "precise note DOES NOT call the highlight hook when the note is aborted"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ ;; this is how you trap a C-g
+ (condition-case nil
+ (with-simulated-input "C-g" (org-noter-insert-precise-note))
+ (quit nil))
+ (expect 'org-noter-core-test-add-highlight :not :to-have-been-called))))
+
+ )
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (describe "session creation"
+ ;; check that the narrowed buffer is named correctly
+ (it "narrowed buffer is named correctly"
+ (with-mock-contents
+ mock-contents-simple-notes-file-with-a-single-note
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (let* ((session org-noter--session))
+ (expect (buffer-name (org-noter--session-notes-buffer session)) :to-equal "Notes of solove-nothing-to-hide")
+ ))))
+
+ ;; check that session properties are set correctly
+ (it "session properties are set correctly"
+ (with-mock-contents
+ mock-contents-simple-notes-file-with-a-single-note
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (let* ((session org-noter--session))
+ (expect (org-noter--session-property-text session) :to-equal "pubs/solove-nothing-to-hide.pdf")
+ (expect (org-noter--session-display-name session) :to-equal "solove-nothing-to-hide")
+ (expect (org-noter--session-notes-file-path session) :to-equal org-noter-test-file)
+ (expect (buffer-file-name (org-noter--session-notes-buffer session)) :to-equal org-noter-test-file)
+ ;; TODO: Need test-specific-major mode somehow?
+ ;; (expect (org-noter--session-doc-mode session) :to-equal 'org-core-test)
+ ))))
+
+ )
+
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (describe "view-info"
+ (it "can get view info"
+ (with-mock-contents
+ mock-contents-simple-notes-file-with-a-single-note
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (let* ((view-info (org-noter--get-view-info (org-noter--get-current-view))))
+ (expect 'org-noter-core-test-get-current-view :to-have-been-called)
+ ))))
+ )
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (describe "locations"
+ (defvar test-precise-location '(3 1 . 0.1))
+ (defvar test-simple-location '(3 1))
+ (defvar test-extra-precise-location '(4 1 0.1 0.2 0.3))
+
+ (describe "precise locations"
+ (it "can get page from a precise location"
+ (expect (org-noter--get-location-page test-precise-location) :to-equal 3))
+
+ (it "can get top from a precise location"
+ (expect (org-noter--get-location-top test-precise-location) :to-equal 1))
+
+ (it "can get left from a precise location"
+ (expect (org-noter--get-location-left test-precise-location) :to-equal 0.1))
+ )
+
+ (describe "simple locations"
+
+ (it "doesn't get a left location for simple location"
+ (expect (org-noter--get-location-left test-simple-location) :to-equal nil)
+ )
+
+ (it "can get top from a simple location"
+ (expect (org-noter--get-location-top test-simple-location) :to-equal 1))
+
+ (it "can get page from a simple location"
+ (expect (org-noter--get-location-page test-simple-location) :to-equal 3))
+ )
+
+ (describe "extra precise locations"
+ (it "can get page from an extra precise location"
+ (expect (org-noter--get-location-page test-extra-precise-location) :to-equal 4))
+
+ (it "can get top from an extra precise location"
+ (expect (org-noter--get-location-top test-extra-precise-location) :to-equal 1))
+
+
+ (it "can get left from an extra precise location"
+ (expect (org-noter--get-location-left test-extra-precise-location) :to-equal 0.1)))
+ )
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (describe "persistent highlights"
+ (describe "no hooks are setup for precise note highlights"
+ ;; if no hooks for highlights are setup we expect no :HIGHLIGHT: property
+ (before-each
+ (setq org-noter--get-highlight-location-hook '())
+ )
+ (it "can take a precise note without a highlight appearing"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "precise SPC note RET"
+ (org-noter-insert-precise-note))
+ (expect (string-match ":HIGHLIGHT:" (buffer-string)) :to-be nil)))))
+
+
+ (describe "hooks for persistent highlights are setup"
+ ;; setup hooks for highlighting
+ (before-each
+ (add-to-list 'org-noter--get-highlight-location-hook #'org-noter-core-test-get-highlight-location)
+ (spy-on 'org-noter-core-test-get-highlight-location :and-call-through)
+ )
+ ;; now that the hooks for highlights are setup, we expect :HIGHLIGHT: property to appear.
+ (it "can take a precise note WITH a highlight appearing"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "precise SPC note RET"
+ (org-noter-insert-precise-note))
+ (expect (string-match "\\:HIGHLIGHT\\:" (buffer-string)) :not :to-be nil)
+ (expect (string-match "HARDCODED_HIGHLIGHT_LOCATION" (buffer-string)) :not :to-be nil)))))
+ )
+
+
+
+
+ (describe "org-noter basics"
+ (it "can start org-noter with `org-noter` call"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ ;; move to the heading where we're going to invoke org-noter
+ (search-forward "nothing-to-hide")
+ (org-noter))))
+
+ (it "has org-noter-create-session-from-document hook defined"
+ (expect org-noter-create-session-from-document-hook :not :to-be nil))
+ )
+)
diff --git a/tests/org-noter-extra-tests.el b/tests/org-noter-extra-tests.el
new file mode 100644
index 0000000..06e7eab
--- /dev/null
+++ b/tests/org-noter-extra-tests.el
@@ -0,0 +1,52 @@
+
+(add-to-list 'load-path "modules")
+
+(describe "org-noter very custom behavior"
+ (before-each
+ (create-org-noter-test-session)
+ )
+ (describe "with advice"
+ (before-each
+ (setq org-noter-max-short-selected-text-length 700000)
+
+ (define-advice org-noter--insert-heading (:after (level title &optional newlines-number location) add-full-body-quote)
+ "Advice for org-noter--insert-heading.
+
+ When inserting a precise note insert the text of the note in the body as an org mode QUOTE block.
+
+ =org-noter-max-short-length= should be set to a large value to short circuit the normal behavior:
+ =(setq org-noter-max-short-length 80000)="
+
+ ;; this tells us it's a precise note that's being invoked.
+ (if (consp location)
+ (insert (format "#+BEGIN_QUOTE\n%s\n#+END_QUOTE" title))))
+ (create-org-noter-test-session)
+ )
+ (after-each
+ (setq org-noter-max-short-selected-text-length 80)
+ (advice-remove #'org-noter--insert-heading 'org-noter--insert-heading@add-full-body-quote)
+ )
+ (it "should insert the highlighted text as an org-mode QUOTE when advice is enabled."
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ ;; we're not specifying the note title
+ (with-simulated-input "RET"
+ (org-noter-insert-precise-note))
+ (let* ((expected-heading (regexp-quote (format "** %s" (org-trim (replace-regexp-in-string "\n" " " (org-noter-test-get-selected-text nil)))))))
+ (expect (string-match "HARDCODED_HIGHLIGHT_LOCATION" (buffer-string)) :not :to-be nil)
+ (expect (string-match "BEGIN_QUOTE" (buffer-string)) :not :to-be nil)
+ (expect (string-match "END_QUOTE" (buffer-string)) :not :to-be nil)
+ (expect (string-match expected-heading (buffer-string)) :not :to-be nil))))))
+
+
+ (describe "without advice"
+ (it "should revert back to standard title"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "RET"
+ (org-noter-insert-precise-note))
+ (expect (string-match "\\*\\* Notes for page" (buffer-string)) :not :to-be nil))))))
diff --git a/tests/org-noter-location-tests.el b/tests/org-noter-location-tests.el
new file mode 100644
index 0000000..2f10103
--- /dev/null
+++ b/tests/org-noter-location-tests.el
@@ -0,0 +1,56 @@
+(add-to-list 'load-path "modules")
+(require 'org-noter-test-utils)
+
+(defvar mock-contents-simple-notes-file-with-locations
+ "
+:PROPERTIES:
+:ID: FAKE_1
+:END:
+#+TITLE: Test book notes (simple)
+* solove-nothing-to-hide
+:PROPERTIES:
+:NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
+:END:
+** Heading1
+:PROPERTIES:
+:NOTER_PAGE: 40
+:END:
+** Heading2
+:PROPERTIES:
+:NOTER_PAGE: (41 0.09 . 0.16)
+:HIGHLIGHT: #s(pdf-highlight 41 ((0.18050847457627117 0.09406231628453851 0.6957627118644067 0.12110523221634333)))
+:END:
+#+BEGIN_QUOTE
+Test
+#+END_QUOTE
+
+")
+
+
+
+
+(describe "org-noter locations"
+ (describe "basic location parsing works"
+ (before-each
+ )
+
+ (describe "page locations"
+ (before-each
+ (create-org-noter-test-session)
+ )
+ (it "can parse a page location"
+ (with-mock-contents
+ mock-contents-simple-notes-file-with-locations
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (search-forward "Heading2")
+ (expect (org-noter--get-containing-heading) :not :to-be nil)
+ (expect (org-noter--parse-location-property (org-noter--get-containing-element)) :to-equal (read "(41 0.09 . 0.16)"))
+ )
+
+ ))
+
+ )
+
+ )
+ )
diff --git a/tests/org-noter-pdf-tests.el b/tests/org-noter-pdf-tests.el
new file mode 100644
index 0000000..5873993
--- /dev/null
+++ b/tests/org-noter-pdf-tests.el
@@ -0,0 +1,69 @@
+(add-to-list 'load-path "modules")
+(require 'org-noter-test-utils)
+
+
+(defvar expected-highlight-info (make-pdf-highlight :page 747 :coords '(0.1 0.2 0.3 0.4)))
+
+(describe "org-noter-pdf-functionality"
+ ;; todo refactor 👇
+ (describe "location functionality"
+ )
+
+
+ (describe "pdf specific highlight functionality"
+ (before-each
+ (spy-on 'pdf-view-active-region-p :and-return-value t)
+ (spy-on 'pdf-view-active-region :and-return-value '(0.1 0.2 0.3 0.4))
+ (spy-on 'image-mode-window-get :and-return-value 747)
+ )
+
+ (it "can get coordinates from pdf-view"
+ (let ((highlight-info (org-noter-pdf--get-highlight)))
+ (expect 'pdf-view-active-region-p :to-have-been-called)
+ (expect highlight-info :to-equal expected-highlight-info)))
+
+ (describe "highlight persistence"
+ (before-each
+ (create-org-noter-test-session)
+ ;; (create-org-noter-test-session) sets up a highlight hook, so we have to reset it back.
+ ;; this might be ok for now? maybe filter out all "-core-test-" hooks instead?
+ (setq org-noter--get-highlight-location-hook '(org-noter-pdf--get-highlight))
+ )
+ (it "can take a precise note WITH a highlight appearing"
+ (with-mock-contents
+ mock-contents-simple-notes-file
+ '(lambda ()
+ (org-noter-core-test-create-session)
+ (with-simulated-input "precise SPC note RET"
+ (org-noter-insert-precise-note))
+ (ont--log-debug "%s" (buffer-string))
+ (expect (string-match "\\:HIGHLIGHT\\:" (buffer-string)) :not :to-be nil)
+ (expect (string-match (format "%s" expected-highlight-info) (buffer-string)) :not :to-be nil)
+ )
+ )
+ )
+ )
+ )
+
+ (describe "pdf keybinding overrides"
+ (it "C-c C-c called from a PDF document executes in the notes buffer"
+ ;; open `org-noter' session with PDF and notes
+
+ ;; execute `C-c C-c' from document buffer
+
+ ;; check that current window is notes-window, check that
+ ;; last command was `org-ctrl-c-ctrl-c'
+ )
+
+ (it "C-c C-x called from a PDF document executes in the notes buffer"
+ ;; open `org-noter' session with PDF and notes
+
+ ;; execute `C-c C-x ' from document buffer, where
+ ;; \in {C-b, C-v, maybe a few others}
+
+ ;; check that current window is notes-window, check that
+ ;; last command corresponds to the keybinding of C-c C-x
+ ;; .
+ )
+ )
+ )