diff --git a/Cask b/Cask index c224206..e040aaa 100644 --- a/Cask +++ b/Cask @@ -1,5 +1,5 @@ (source gnu) -(source melpa-stable) +(source melpa) (depends-on "buttercup") (depends-on "pdf-tools") diff --git a/README.org b/README.org index edeccc5..96d27e6 100644 --- a/README.org +++ b/README.org @@ -105,6 +105,12 @@ the document to annotate. *** new (all formats) + - ~org-noter-enable-update-renames~ :: Optional feature to update document + paths when renaming or moving document files or notes files. This allows + you to change your mind later about the names and locations of your + document files and notes files without having to manually update all the + links. + - ~org-noter-toggle-notes-window-location~ (~M-T~) :: Toggle between horizontal and vertical document/notes layout. diff --git a/modules/org-noter-djvu.el b/modules/org-noter-djvu.el index eccaacb..ed208e0 100644 --- a/modules/org-noter-djvu.el +++ b/modules/org-noter-djvu.el @@ -33,6 +33,8 @@ (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) diff --git a/modules/org-noter-nov.el b/modules/org-noter-nov.el index e8f6374..9786ffb 100644 --- a/modules/org-noter-nov.el +++ b/modules/org-noter-nov.el @@ -33,6 +33,8 @@ (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 diff --git a/modules/org-noter-pdf.el b/modules/org-noter-pdf.el index a15965e..fa91777 100644 --- a/modules/org-noter-pdf.el +++ b/modules/org-noter-pdf.el @@ -34,6 +34,7 @@ (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 () @@ -501,19 +502,20 @@ current heading inherit the COLUMN_EDGES property." ;;; override some deleterious keybindings in pdf-view-mode. (define-key org-noter-doc-mode-map (kbd "C-c C-c") - (lambda () + (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))) -(defun org-noter-pdf--execute-CxCc-in-notes () - "Execute C-c C-x prefixed command in notes 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)))) -(define-key org-noter-doc-mode-map (kbd "C-c C-x") 'org-noter-pdf--execute-CxCc-in-notes) +(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 index f1cfbc4..50dec06 100644 --- a/org-noter-core.el +++ b/org-noter-core.el @@ -60,6 +60,10 @@ :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 @@ -339,7 +343,23 @@ 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) + :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 @@ -1662,7 +1682,7 @@ DOCUMENT-PATH is a path to a document file." (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)) + (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))))))) @@ -1734,6 +1754,150 @@ mode." 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) @@ -2384,6 +2548,28 @@ As such, it will only work when the notes window exists." (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: diff --git a/org-noter.el b/org-noter.el index 2693098..6e15d4c 100644 --- a/org-noter.el +++ b/org-noter.el @@ -267,7 +267,10 @@ DOCUMENT-FILE-NAME is the document filename." (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)))))