org-capture: Add template hook properties

* lisp/org-capture.el (org-capture-templates): Document template hook properties.
(org-capture-finalize): Execute :prepare/:before/:after-finalize functions.
(org-capture-place-template): Execute :hook functions.

* doc/org-manual.org: Document template hook properties.

* etc/ORG-NEWS: Add news entry for template hook properties.

* testing/lisp/test-org-capture.el: Add tests for template hook properties.
This commit is contained in:
Nicholas Vollmer 2022-09-27 05:44:33 -04:00 committed by Ihor Radchenko
parent 0be36ac13e
commit 7f3a6cf6e7
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
4 changed files with 102 additions and 0 deletions

View File

@ -7929,6 +7929,26 @@ Now lets look at the elements of a template definition. Each entry in
- ~:refile-targets~ :: Temporarily set ~org-refile-targets~ to the - ~:refile-targets~ :: Temporarily set ~org-refile-targets~ to the
value of this property. value of this property.
- ~:hook~ ::
A nullary function or list of nullary functions run before
~org-capture-mode-hook~ when the template is selected.
- ~:prepare-finalize~ ::
A nullary function or list of nullary functions run before
~org-capture-prepare-finalize-hook~ when the template is selected.
- ~:before-finalize~ ::
A nullary function or list of nullary functions run before
~org-capture-before-finalize-hook~ when the template is selected.
- ~:after-finalize~ ::
A nullary function or list of nullary functions run before
~org-capture-after-finalize-hook~ when the template is selected.
**** Template expansion **** Template expansion
:PROPERTIES: :PROPERTIES:
:DESCRIPTION: Filling in information about time and context. :DESCRIPTION: Filling in information about time and context.

View File

@ -663,6 +663,13 @@ When exiting capture mode via ~org-capture-refile~, the variable
~org-refile-targets~ will be temporarily bound to the value of this ~org-refile-targets~ will be temporarily bound to the value of this
template option. template option.
*** Add Capture template hook properties
Capture templates can now attach template specific hooks via the
following properties: ~:hook~, ~:prepare-finalize~,
~:before-finalize~, ~:after-finalize~. These nullary functions run
prior to their global counterparts for the selected template.
*** New startup options =#+startup: show<n>levels= *** New startup options =#+startup: show<n>levels=
These startup options complement the existing =overview=, =content=, These startup options complement the existing =overview=, =content=,

View File

@ -297,6 +297,21 @@ properties are:
:no-save Do not save the target file after finishing the capture. :no-save Do not save the target file after finishing the capture.
:hook A nullary function or list of nullary functions run before
`org-capture-mode-hook' when the template is selected.
:prepare-finalize A nullary function or list of nullary functions run before
`org-capture-prepare-finalize-hook'
when the template is selected.
:before-finalize A nullary function or list of nullary functions run before
`org-capture-before-finalize-hook'
when the template is selected.
:after-finalize A nullary function or list of nullary functions run before
`org-capture-after-finalize-hook'
when the template is selected.
The template defines the text to be inserted. Often this is an The template defines the text to be inserted. Often this is an
Org mode entry (so the first line should start with a star) that Org mode entry (so the first line should start with a star) that
will be filed as a child of the target headline. It can also be will be filed as a child of the target headline. It can also be
@ -741,6 +756,17 @@ of the day at point (if any) or the current HH:MM time."
(format "* Template function %S not found" f))) (format "* Template function %S not found" f)))
(_ "* Invalid capture template")))) (_ "* Invalid capture template"))))
(defun org-capture--run-template-functions (keyword &optional local)
"Run funcitons associated with KEYWORD on template's plist.
For valid values of KEYWORD see `org-capture-templates'.
If LOCAL is non-nil use the buffer-local value of `org-capture-plist'."
;; Used in place of `run-hooks' because these functions have no associated symbol.
;; They are stored directly on `org-capture-plist'.
(let ((value (org-capture-get keyword local)))
(if (functionp value)
(funcall value)
(mapc #'funcall value))))
(defun org-capture-finalize (&optional stay-with-capture) (defun org-capture-finalize (&optional stay-with-capture)
"Finalize the capture process. "Finalize the capture process.
With prefix argument STAY-WITH-CAPTURE, jump to the location of the With prefix argument STAY-WITH-CAPTURE, jump to the location of the
@ -752,6 +778,7 @@ captured item after finalizing."
(buffer-base-buffer (current-buffer))) (buffer-base-buffer (current-buffer)))
(error "This does not seem to be a capture buffer for Org mode")) (error "This does not seem to be a capture buffer for Org mode"))
(org-capture--run-template-functions :prepare-finalize 'local)
(run-hooks 'org-capture-prepare-finalize-hook) (run-hooks 'org-capture-prepare-finalize-hook)
;; Update `org-capture-plist' with the buffer-local value. Since ;; Update `org-capture-plist' with the buffer-local value. Since
@ -821,6 +848,7 @@ captured item after finalizing."
;; the indirect buffer has been killed. ;; the indirect buffer has been killed.
(org-capture-store-last-position) (org-capture-store-last-position)
(org-capture--run-template-functions :before-finalize 'local)
;; Run the hook ;; Run the hook
(run-hooks 'org-capture-before-finalize-hook)) (run-hooks 'org-capture-before-finalize-hook))
@ -869,6 +897,9 @@ captured item after finalizing."
;; Restore the window configuration before capture ;; Restore the window configuration before capture
(set-window-configuration return-wconf)) (set-window-configuration return-wconf))
;; Do not use the local arg to `org-capture--run-template-functions' here.
;; The buffer-local value has been stored on `org-capture-plist'.
(org-capture--run-template-functions :after-finalize)
(run-hooks 'org-capture-after-finalize-hook) (run-hooks 'org-capture-after-finalize-hook)
;; Special cases ;; Special cases
(cond (cond
@ -1145,6 +1176,7 @@ may have been stored before."
(`item (org-capture-place-item)) (`item (org-capture-place-item))
(`checkitem (org-capture-place-item))) (`checkitem (org-capture-place-item)))
(setq-local org-capture-current-plist org-capture-plist) (setq-local org-capture-current-plist org-capture-plist)
(org-capture--run-template-functions :hook 'local)
(org-capture-mode 1)) (org-capture-mode 1))
(defun org-capture-place-entry () (defun org-capture-place-entry ()

View File

@ -754,5 +754,48 @@
(org-capture nil "t") (org-capture nil "t")
(buffer-string)))))) (buffer-string))))))
(ert-deftest test-org-capture/template-specific-hooks ()
"Test template-specific hook execution."
;; Runs each template hook prior to corresponding global hook
(should
(equal "hook\nglobal-hook\nprepare\nglobal-prepare
before\nglobal-before\nafter\nglobal-after"
(org-test-with-temp-text-in-file ""
(let* ((file (buffer-file-name))
(org-capture-mode-hook
'((lambda () (insert "global-hook\n"))))
(org-capture-prepare-finalize-hook
'((lambda () (insert "global-prepare\n"))))
(org-capture-before-finalize-hook
'((lambda () (insert "global-before\n"))))
(org-capture-after-finalize-hook
'((lambda () (with-current-buffer
(org-capture-get :buffer)
(goto-char (point-max))
(insert "global-after")))))
(org-capture-templates
`(("t" "Test" plain (file ,file) ""
:hook (lambda () (insert "hook\n"))
:prepare-finalize (lambda () (insert "prepare\n"))
:before-finalize (lambda () (insert "before\n"))
:after-finalize (lambda () (with-current-buffer
(org-capture-get :buffer)
(goto-char (point-max))
(insert "after\n")))
:immediate-finish t))))
(org-capture nil "t")
(buffer-string)))))
;; Accepts a list of nullary functions
(should
(equal "one\ntwo"
(org-test-with-temp-text-in-file ""
(let* ((file (buffer-file-name))
(org-capture-templates
`(("t" "Test" plain (file ,file) ""
:hook ((lambda () (insert "one\n"))
(lambda () (insert "two")))))))
(org-capture nil "t")
(buffer-string))))))
(provide 'test-org-capture) (provide 'test-org-capture)
;;; test-org-capture.el ends here ;;; test-org-capture.el ends here