>From 90f0c68e1149512b51230fd44ad728b38e5f088e Mon Sep 17 00:00:00 2001 From: Nicholas Vollmer Date: Tue, 27 Sep 2022 05:44:33 -0400 Subject: [PATCH] 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. --- doc/org-manual.org | 20 +++++++++++++++ etc/ORG-NEWS | 6 +++++ lisp/org-capture.el | 32 ++++++++++++++++++++++++ testing/lisp/test-org-capture.el | 43 ++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/doc/org-manual.org b/doc/org-manual.org index ab8a295e5..6a857529c 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -7838,6 +7838,26 @@ Now lets look at the elements of a template definition. Each entry in - ~:refile-targets :: Temporarily set ~org-refile-targets~ to the 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 :PROPERTIES: :DESCRIPTION: Filling in information about time and context. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index cab64317f..1a482c3c7 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -657,6 +657,12 @@ When exiting capture mode via ~org-capture-refile~, the variable ~org-refile-targets~ will be temporarily bound to the value of this 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: showlevels= These startup options complement the existing =overview=, =content=, diff --git a/lisp/org-capture.el b/lisp/org-capture.el index 428d0ac0e..4b6b51b39 100644 --- a/lisp/org-capture.el +++ b/lisp/org-capture.el @@ -297,6 +297,21 @@ properties are: :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 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 @@ -740,6 +755,17 @@ of the day at point (if any) or the current HH:MM time." (format "* Template function %S not found" f))) (_ "* 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) "Finalize the capture process. With prefix argument STAY-WITH-CAPTURE, jump to the location of the @@ -751,6 +777,7 @@ captured item after finalizing." (buffer-base-buffer (current-buffer))) (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) ;; Update `org-capture-plist' with the buffer-local value. Since @@ -823,6 +850,7 @@ captured item after finalizing." ;; the indirect buffer has been killed. (org-capture-store-last-position) + (org-capture--run-template-functions :before-finalize 'local) ;; Run the hook (run-hooks 'org-capture-before-finalize-hook)) @@ -871,6 +899,9 @@ captured item after finalizing." ;; Restore the window configuration before capture (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) ;; Special cases (cond @@ -1147,6 +1178,7 @@ may have been stored before." (`item (org-capture-place-item)) (`checkitem (org-capture-place-item))) (setq-local org-capture-current-plist org-capture-plist) + (org-capture--run-template-functions :hook 'local) (org-capture-mode 1)) (defun org-capture-place-entry () diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el index 47c7ed129..44702c3ce 100644 --- a/testing/lisp/test-org-capture.el +++ b/testing/lisp/test-org-capture.el @@ -754,5 +754,48 @@ (org-capture nil "t") (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) ;;; test-org-capture.el ends here -- 2.37.3