From 7f3a6cf6e72fe9968c6ef32211c754b7fe0172b6 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 | 7 ++++++ lisp/org-capture.el | 32 ++++++++++++++++++++++++ testing/lisp/test-org-capture.el | 43 ++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/doc/org-manual.org b/doc/org-manual.org index da7d6246e..87ce674fb 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -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 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 74095d101..34ec09927 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -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 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 cc6e2bd92..95b64ffd4 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 @@ -741,6 +756,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 @@ -752,6 +778,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 @@ -821,6 +848,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)) @@ -869,6 +897,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 @@ -1145,6 +1176,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