Skip to content

Commit

Permalink
Merge remote-tracking branch 'org-noter/master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
petermao committed Jan 17, 2024
2 parents c90909c + 8be3763 commit eb75126
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cask
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@

(depends-on "log4e")
(depends-on "undercover")

(depends-on "org-roam")
2 changes: 1 addition & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
acting like notes that are made /inside/ the document. Also, taking notes is
very simple: just press =i= and annotate away!

Org-noter is compatible with [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Document-View.html][DocView]], [[https://github.com/politza/pdf-tools][PDF Tools]], [[https://depp.brause.cc/nov.el/][Nov.el]], and
Org-noter is compatible with [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Document-View.html][DocView]], [[https://github.com/vedang/pdf-tools][PDF Tools]], [[https://depp.brause.cc/nov.el/][Nov.el]], and
[[DJVU-read][DJVU-image-mode]]. These modes make it possible to annotate *PDF*, *EPUB*,
*Microsoft Office*, DVI, PS, OpenDocument, and DJVU formatted files. Note
that PDF support is our prime goal. Other format have been supported by other
Expand Down
117 changes: 117 additions & 0 deletions modules/org-noter-org-roam.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
;;; org-noter-org-roam --- org-roam support for org-noter -*- lexical-binding: t; -*-

;; Copyright (C) 2023 Dmitry Markushevich

;; This file is not part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This module adds org-roam integration to org-noter

;;; Code:

(require 'cl-lib)
(require 'org-roam-node)

(defun org-noter--get-filename-for-org-roam-node ()
"Use org-roam to specify a node."
(let* ((templates (list (append (car org-roam-capture-templates) '(:immediate-finish t))))
(node (org-roam-node-read))
(_ (org-roam-capture-
:node node
:info nil
:templates templates
:props nil))
(node-id (org-roam-node-id node))
(file-path-for-new-entry (org-roam-node-file (org-roam-node-from-id node-id))))
(message "%s" file-path-for-new-entry)
file-path-for-new-entry))


(defun org-noter--create-session-from-document-file-supporting-org-roam
(&optional arg doc-path)
"Main point of integration with org-noter.
This is a hook function that is to be assigned
to `org-noter-create-session-from-document-hook' to enable org-roam integration:
`(setq org-noter-create-session-from-document-hook '(org-noter--create-session-from-document-file-supporting-org-roam)'
Alternatively, you can call the `org-noter-enable-org-roam-integration'.
ARG is not current used but here for compatibility reasons.
DOC-PATH is the path to the document (pdf)."
(let* ((file-path-for-org-roam-node (org-noter--get-filename-for-org-roam-node))
(_ (message "[d] opening up notes: %s doc: %s" file-path-for-org-roam-node doc-path))
;; create or find a top level heading for the document and return it
(top-level-heading-for-doc-position (with-current-buffer (find-file-noselect file-path-for-org-roam-node)
(org-noter--find-create-top-level-heading-for-doc doc-path (file-name-base doc-path)))))
(message "going to pos: %s" top-level-heading-for-doc-position)
(with-current-buffer (find-file-noselect file-path-for-org-roam-node)
(goto-char top-level-heading-for-doc-position)
(org-noter))))




(defun org-noter--find-top-level-heading-for-document-path (doc-path)
"Given a DOC-PATH check to see if there's a top level heading for it.
It returns the point for the heading (if found) \"nil\" otherwise."
(let ((found-heading-position nil))
(org-with-point-at (point-min)
(condition-case nil
;; look for NOTER_DOCUMENT property that matches the doc-path
(while (and (not found-heading-position)
(re-search-forward (org-re-property org-noter-property-doc-file)))
(let ((current-file-name (expand-file-name (match-string 3)))
(looking-for-filename (expand-file-name doc-path)))
(when (file-equal-p current-file-name looking-for-filename)
(setq found-heading-position (point)))))
(search-failed ;; when re=search-forward hits the end it throws an error which we should catch
(message "This buffer doesn't seem to have a matching NOTER_DOCUMENT heading.") nil)))
found-heading-position))


(defun org-noter--find-create-top-level-heading-for-doc (doc-path desired-heading)
"In current buffer, look for a top level heading for document at DOC-PATH.
If one is not found, DESIRED-HEADING is created and it's position is returned"
(let* ((top-level-heading-for-doc-position (org-noter--find-top-level-heading-for-document-path doc-path)))
;; does this buffer have a top level notes heading for this document?
(if (eq top-level-heading-for-doc-position nil)
(org-noter--create-notes-heading desired-heading doc-path)
top-level-heading-for-doc-position)))


;; TODO How is this different from org-noter--insert-heading?
;; org-noter--insert-heading doesn't deal with top level headings.
(defun org-noter--create-notes-heading (notes-heading document-path)
"Create a top level notes heading for the document.
NOTES-HEADING is the headline, DOCUMENT-PATH is used for the
NOTER_DOCUMENT property. Return the point where the heading was inserted."
(cl-assert notes-heading t "notes-heading cannot be nil. we can't insert a nil heading.")
(goto-char (point-max))
(insert (if (save-excursion (beginning-of-line) (looking-at "[[:space:]]*$")) "" "\n")
"* " notes-heading )
(org-entry-put nil org-noter-property-doc-file
(expand-file-name document-path))
(point))



(provide 'org-noter-org-roam)

;;; org-noter-org-roam.el ends here
2 changes: 1 addition & 1 deletion modules/org-noter-pdf.el
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ 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))
(column-edges-string (when (derived-mode-p 'org-mode) (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))
Expand Down
10 changes: 9 additions & 1 deletion org-noter.el
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
;; Dmitry M <[email protected]>
;; 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"))
;; Package-Requires: ((emacs "24.4") (cl-lib "0.6") (org "9.4"))
;; Version: 1.5.0

;; This file is not part of GNU Emacs.
Expand Down Expand Up @@ -308,6 +308,14 @@ marked file."
(bury-buffer))
(other-frame 1)))


(defun org-noter-enable-org-roam-integration ()
"Enable org-roam integration."
(interactive)
(load "org-noter-org-roam")
(setq org-noter-create-session-from-document-hook
'(org-noter--create-session-from-document-file-supporting-org-roam)))

(provide 'org-noter)

;;; org-noter.el ends here
120 changes: 120 additions & 0 deletions tests/org-noter-org-roam-tests.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
(require 'org-noter-test-utils)
(require 'org-noter-org-roam)

(describe "org-roam integration"
(before-each
(create-org-noter-test-session)
)

(describe "org-roam"
(before-each
;; org-noter uses file-equal-p that looks at the filesystem. We need real files to verify this functionality.
(shell-command "mkdir -p /tmp/pubs/ && touch /tmp/pubs/solove-nothing-to-hide.pdf && touch /tmp/test.pdf"))

(describe "top level heading insertion"
(it "can insert a top level heading at the end of the file"
(with-mock-contents
mock-contents-simple-notes-file
'(lambda ()
(org-noter--create-notes-heading "ADOCUMENT" "/tmp/file")
(expect (string-match "ADOCUMENT" (buffer-string)) :not :to-be nil)
(expect (string-match "/tmp/file" (buffer-string)) :not :to-be nil)
;; ADOCUMENT should come after solove-nothing-to-hide
(expect (string-match "solove-nothing-to-hide" (buffer-string)) :to-be-less-than
(string-match "ADOCUMENT" (buffer-string)))
(message (buffer-string)))))

(it "can find an existing heading without creating a new one"
(with-mock-contents
mock-contents-simple-notes-file
'(lambda ()
(let* ((found-heading-pos (org-noter--find-create-top-level-heading-for-doc "/tmp/test.pdf" "solove-nothing-to-hide")))
(message "\n00 ----")
(goto-char found-heading-pos)
(insert "!!")
(message (buffer-string))
(message "\n00 ----")

(expect found-heading-pos :to-be 141)
(message "----")
(message (buffer-string))
(message "---- %s" (length (buffer-string)))))))


(it "can create a new heading"
(with-mock-contents
mock-contents-simple-notes-file
'(lambda ()
(expect
;; org-noter-test-file is defined in test-utils.
(org-noter--find-create-top-level-heading-for-doc "/tmp/some-other-pdf-file.pdf" "SOME HEADING")
:to-be 162)
(message "----")
(message (buffer-string))
(message "---- %s" (length (buffer-string)))

)))
)


(describe "identifying top level headlines"
(before-each
;; org-noter uses file-equal-p that looks at the filesystem. We need real files to verify this functionality.
(shell-command "mkdir -p /tmp/pubs/ && touch /tmp/pubs/solove-nothing-to-hide.pdf")
)

(it "can find the top level headline for a specified document and return the position"
(with-mock-contents
mock-contents-simple-notes-file
'(lambda ()
(message "\n11 ----")
(insert "!!")
(message (buffer-string))
(message "\n11 ----")
(expect
(org-noter--find-top-level-heading-for-document-path "/tmp/test.pdf")
:to-be 143)
(message (buffer-string)))))


(it "return nil for a non existent top level heading"
(with-mock-contents
mock-contents-simple-notes-file
'(lambda ()
(expect
(org-noter--find-top-level-heading-for-document-path "/FAKE/PATH/DOESNT/EXIST")
:to-be nil)
(message (buffer-string)))))
)




)


(describe "org-noter integration"
(before-each
;; org-noter uses file-equal-p that looks at the filesystem. We need real files to verify this functionality.
(shell-command "mkdir -p /tmp/pubs/ && touch /tmp/pubs/solove-nothing-to-hide.pdf"))

;; mocking a lot of stuff, for integration sake.
;; ideally we'd split up functions to be somewhat smaller to ease testing.
(it "executes org-noter"
(spy-on 'org-noter--find-create-top-level-heading-for-doc :and-call-fake (lambda (doc-path desired-heading) 1001))
(spy-on 'org-noter--get-filename-for-org-roam-node :and-call-fake (lambda () "/tmp/pubs/notes.org"))
(spy-on 'org-noter)
(with-mock-contents
mock-contents-simple-notes-file
'(lambda ()
(write-region (point-min) (point-max) "/tmp/pubs/notes.org")))
(org-noter--create-session-from-document-file-supporting-org-roam nil "/tmp/pubs/solove-nothing-to-hide.pdf")
(expect 'org-noter :to-have-been-called))
)

(it "sets the hook correctly when org-roam integration is enabled"
(org-noter-enable-org-roam-integration)
(expect org-noter-create-session-from-document-hook :to-equal '(org-noter--create-session-from-document-file-supporting-org-roam)))


)

0 comments on commit eb75126

Please sign in to comment.