(feat!)capture: change id creation to headline on entry-type capture-templates

This commit is contained in:
Jethro Kuan
2022-01-20 14:46:22 -08:00
committed by Dustin Farris
parent 89dfaef38b
commit ed94524964
4 changed files with 248 additions and 28 deletions

View File

@@ -1,5 +1,8 @@
# Changelog # Changelog
## 2.3.2
- [#2056](https://github.com/org-roam/org-roam/pull/2056) capture: IDs are now created for entries in capture templates
## 2.3.1 (2025-06-26) ## 2.3.1 (2025-06-26)
* (fix): Use correct type specifications to suppress warnings by @okomestudio in https://github.com/org-roam/org-roam/pull/2522 * (fix): Use correct type specifications to suppress warnings by @okomestudio in https://github.com/org-roam/org-roam/pull/2522

View File

@@ -339,11 +339,12 @@ In this case, interactive selection will be bypassed."
(when goto (run-hooks 'org-roam-dailies-find-file-hook))) (when goto (run-hooks 'org-roam-dailies-find-file-hook)))
(add-hook 'org-roam-capture-preface-hook #'org-roam-dailies--override-capture-time-h) (add-hook 'org-roam-capture-preface-hook #'org-roam-dailies--override-capture-time-h)
(defun org-roam-dailies--override-capture-time-h () (defun org-roam-dailies--override-capture-time-h ()
"Override the `:default-time' with the time from `:override-default-time'." "Override the `:default-time' with the time from `:override-default-time'."
(prog1 nil (when (org-roam-capture--get :override-default-time)
(when (org-roam-capture--get :override-default-time) (org-capture-put :default-time (org-roam-capture--get :override-default-time)))
(org-capture-put :default-time (org-roam-capture--get :override-default-time))))) nil)
;;; Bindings ;;; Bindings
(defvar org-roam-dailies-map (make-sparse-keymap) (defvar org-roam-dailies-map (make-sparse-keymap)

View File

@@ -466,22 +466,21 @@ processing by `org-capture'.
Note: During the capture process this function is run by Note: During the capture process this function is run by
`org-capture-set-target-location', as a (function ...) based `org-capture-set-target-location', as a (function ...) based
capture target." capture target."
(let ((id (cond ((run-hook-with-args-until-success 'org-roam-capture-preface-hook)) (if-let ((id (run-hook-with-args-until-success 'org-roam-capture-preface-hook)))
(t (org-roam-capture--setup-target-location))))) (org-roam-capture--put :id id)
(org-roam-capture--adjust-point-for-capture-type) (org-roam-capture--setup-target-location))
(let ((template (org-capture-get :template))) (let ((template (org-capture-get :template)))
(when (stringp template) (when (stringp template)
(org-capture-put (org-capture-put
:template :template
(org-roam-capture--fill-template template)))) (org-roam-capture--fill-template template))))
(org-roam-capture--put :id id) (org-roam-capture--put :finalize (or (org-capture-get :finalize)
(org-roam-capture--put :finalize (or (org-capture-get :finalize) (org-roam-capture--get :finalize))))
(org-roam-capture--get :finalize)))))
(defun org-roam-capture--setup-target-location () (defun org-roam-capture--setup-target-location ()
"Initialize the buffer, and goto the location of the new capture. "Initialize the buffer, and goto the location of the new capture."
Return the ID of the location." (let ((target-entry-p t)
(let (p new-file-p) p new-file-p id)
(pcase (org-roam-capture--get-target) (pcase (org-roam-capture--get-target)
(`(file ,path) (`(file ,path)
(setq path (org-roam-capture--target-truepath path) (setq path (org-roam-capture--target-truepath path)
@@ -489,7 +488,8 @@ Return the ID of the location."
(when new-file-p (org-roam-capture--put :new-file path)) (when new-file-p (org-roam-capture--put :new-file path))
(set-buffer (org-capture-target-buffer path)) (set-buffer (org-capture-target-buffer path))
(widen) (widen)
(setq p (goto-char (point-min)))) (setq p (goto-char (point-min))
target-entry-p nil))
(`(file+olp ,path ,olp) (`(file+olp ,path ,olp)
(setq path (org-roam-capture--target-truepath path) (setq path (org-roam-capture--target-truepath path)
new-file-p (org-roam-capture--new-file-p path)) new-file-p (org-roam-capture--new-file-p path))
@@ -507,7 +507,8 @@ Return the ID of the location."
(org-roam-capture--put :new-file path) (org-roam-capture--put :new-file path)
(insert (org-roam-capture--fill-template head 'ensure-newline))) (insert (org-roam-capture--fill-template head 'ensure-newline)))
(widen) (widen)
(setq p (goto-char (point-min)))) (setq p (goto-char (point-min))
target-entry-p nil))
(`(file+head+olp ,path ,head ,olp) (`(file+head+olp ,path ,head ,olp)
(setq path (org-roam-capture--target-truepath path) (setq path (org-roam-capture--target-truepath path)
new-file-p (org-roam-capture--new-file-p path)) new-file-p (org-roam-capture--new-file-p path))
@@ -569,17 +570,45 @@ Return the ID of the location."
(user-error "No node with title or id \"%s\"" title-or-id)))) (user-error "No node with title or id \"%s\"" title-or-id))))
(set-buffer (org-capture-target-buffer (org-roam-node-file node))) (set-buffer (org-capture-target-buffer (org-roam-node-file node)))
(goto-char (org-roam-node-point node)) (goto-char (org-roam-node-point node))
(setq p (org-roam-node-point node))))) (setq p (org-roam-node-point node)
target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))))
;; Setup `org-id' for the current capture target and return it back to the ;; Setup `org-id' for the current capture target and return it back to the
;; caller. ;; caller.
(save-excursion ;; Unless it's an entry type, then we want to create an ID for the entry instead
(goto-char p) (pcase (org-capture-get :type)
(if-let ((id (org-entry-get p "ID"))) ('entry
(setf (org-roam-node-id org-roam-capture--node) id) (advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
(org-entry-put p "ID" (org-roam-node-id org-roam-capture--node))) (org-roam-capture--put :new-node-p t)
(prog1 (setq id (org-roam-node-id org-roam-capture--node)))
(org-id-get) (_
(run-hooks 'org-roam-capture-new-node-hook))))) (save-excursion
(goto-char p)
(unless (org-entry-get p "ID")
(org-roam-capture--put :new-node-p t))
(setq id (or (org-entry-get p "ID")
(org-roam-node-id org-roam-capture--node)))
(setf (org-roam-node-id org-roam-capture--node) id)
(org-entry-put p "ID" id))))
(org-roam-capture--put :id id)
(org-roam-capture--put :target-entry-p target-entry-p)
(advice-add #'org-capture-place-template :before #'org-roam-capture--set-target-entry-p-a)
(advice-add #'org-capture-place-template :after #'org-roam-capture-run-new-node-hook-a)))
(defun org-roam-capture--set-target-entry-p-a (_)
"Correct `:target-entry-p' in Org-capture template based on `:target.'."
(org-capture-put :target-entry-p (org-roam-capture--get :target-entry-p))
(advice-remove #'org-capture-place-template #'org-roam-capture--set-target-entry-p-a))
(defun org-roam-capture-run-new-node-hook-a (_)
"Advice to run after the Org-capture template is placed."
(when (org-roam-capture--get :new-node-p)
(run-hooks 'org-roam-capture-new-node-hook))
(advice-remove #'org-capture-place-template #'org-roam-capture--place-template-a))
(defun org-roam-capture--create-id-for-entry ()
"Create the ID for the new entry."
(org-entry-put (point) "ID" (org-roam-capture--get :id))
(advice-remove #'org-capture-place-entry #'org-roam-capture--create-id-for-entry))
(defun org-roam-capture--get-target () (defun org-roam-capture--get-target ()
"Get the current capture :target for the capture template in use." "Get the current capture :target for the capture template in use."

View File

@@ -58,6 +58,193 @@
(org-roam-capture--fill-template (lambda () "foo")) (org-roam-capture--fill-template (lambda () "foo"))
:to-equal "foo"))) :to-equal "foo")))
(describe "org-roam-capture entry-type ID creation"
(it "creates ID for entry-type captures"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal))
capture-id)
(unwind-protect
(progn
;; Create initial file content
(with-temp-file test-file
(insert "#+TITLE: Test File\n\n* Parent Heading\n:PROPERTIES:\n:ID: parent-id\n:END:\n"))
;; Mock org-capture context and get-target
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'entry)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file))))
;; Call the setup function
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(setq capture-id (org-roam-capture--get :id)))
;; Verify ID was created and stored for entry type
(expect capture-id :not :to-be nil)
(expect (org-roam-capture--get :new-node-p) :to-be t)))
(delete-directory temp-dir t))))
(it "creates ID at target for non-entry-type captures"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test-plain.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal))
capture-id)
(unwind-protect
(progn
;; Create initial empty file
(with-temp-file test-file
(insert "#+TITLE: Test\n"))
;; Mock org-capture context for plain type
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'plain)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file))))
;; Call the setup function
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(setq capture-id (org-roam-capture--get :id)))
;; For non-entry types, ID should be created at the target location
(expect capture-id :not :to-be nil)
(expect (org-roam-capture--get :new-node-p) :to-be t)))
(delete-directory temp-dir t)))))
(describe "org-roam-capture advice functions"
:var ((org-roam-capture--info))
(before-each
(setq org-roam-capture--info (make-hash-table :test 'equal)))
(it "org-roam-capture--create-id-for-entry creates and removes advice"
(cl-letf* ((entry-id nil)
((symbol-function 'org-entry-put)
(lambda (pom prop val)
(when (string= prop "ID")
(setq entry-id val))))
((symbol-function 'org-roam-capture--get)
(lambda (prop)
(if (eq prop :id)
"test-id-123"
nil))))
;; Add the advice
(advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
;; Call the function
(org-roam-capture--create-id-for-entry)
;; Verify ID was set and advice removed
(expect entry-id :to-equal "test-id-123")
(expect (advice-member-p #'org-roam-capture--create-id-for-entry
#'org-capture-place-entry)
:to-be nil)))
(it "org-roam-capture--set-target-entry-p-a sets and removes advice"
(cl-letf* ((captured-value nil)
((symbol-function 'org-capture-put)
(lambda (prop val)
(when (eq prop :target-entry-p)
(setq captured-value val))))
((symbol-function 'org-roam-capture--get)
(lambda (prop)
(if (eq prop :target-entry-p)
t
nil))))
;; Add the advice
(advice-add #'org-capture-place-template :before #'org-roam-capture--set-target-entry-p-a)
;; Call the function
(org-roam-capture--set-target-entry-p-a nil)
;; Verify value was set and advice removed
(expect captured-value :to-be t)
(expect (advice-member-p #'org-roam-capture--set-target-entry-p-a
#'org-capture-place-template)
:to-be nil)))
(it "org-roam-capture-run-new-node-hook-a runs hook when new node"
(let ((hook-ran nil))
(cl-letf* (((symbol-function 'org-roam-capture--get)
(lambda (prop)
(if (eq prop :new-node-p)
t
nil))))
;; Add test hook
(add-hook 'org-roam-capture-new-node-hook
(lambda () (setq hook-ran t)))
;; Add the advice
(advice-add #'org-capture-place-template :after #'org-roam-capture-run-new-node-hook-a)
;; Call the function
(org-roam-capture-run-new-node-hook-a nil)
;; Verify hook ran
(expect hook-ran :to-be t)
;; Clean up
(remove-hook 'org-roam-capture-new-node-hook
(lambda () (setq hook-ran t)))))))
(describe "org-roam-capture target-entry-p detection"
(it "detects entry target for file+olp"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test-olp.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal)))
(unwind-protect
(progn
(with-temp-file test-file
(insert "* Level 1\n** Level 2\n"))
(cl-letf* (((symbol-function 'org-roam-capture--get-target)
(lambda () `(file+olp ,test-file ("Level 1" "Level 2"))))
((symbol-function 'org-capture-get)
(lambda (prop) nil)))
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(expect (org-roam-capture--get :target-entry-p) :to-be t))))
(delete-directory temp-dir t))))
(it "detects non-entry target for file"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test-file.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal)))
(unwind-protect
(progn
(with-temp-file test-file
(insert "#+TITLE: Test\n"))
(cl-letf* (((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file)))
((symbol-function 'org-capture-get)
(lambda (prop) nil)))
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(expect (org-roam-capture--get :target-entry-p) :to-be nil))))
(delete-directory temp-dir t)))))
(provide 'test-org-roam-capture) (provide 'test-org-roam-capture)
;;; test-org-roam-capture.el ends here ;;; test-org-roam-capture.el ends here