diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c3bd2..bdff809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - [#863](https://github.com/org-roam/org-roam/pull/863) Display outline hierarchy in backlinks buffer - [#898](https://github.com/org-roam/org-roam/pull/898) Add `org-roam-random-note` to browse a random note. - [#900](https://github.com/org-roam/org-roam/pull/900) Support and index all valid org links +- [#966](https://github.com/org-roam/org-roam/pull/966) Enable nested captures ### Bugfixes diff --git a/org-roam-capture.el b/org-roam-capture.el index d4ad421..4b778d8 100644 --- a/org-roam-capture.el +++ b/org-roam-capture.el @@ -280,30 +280,76 @@ Template string :\n%v") ((const :format "%v " :table-line-pos) (string)) ((const :format "%v " :kill-buffer) (const t)))))))) +(defun org-roam-capture-p () + "Return t if the current capture process is an Org-roam capture. +This function is to only be called when org-capture-plist is +valid for the capture (i.e. initialization, and finalization of +the capture)." + (plist-get org-capture-plist :org-roam)) + (defun org-roam-capture--get (keyword) - "Gets the value for KEYWORD from the `org-roam-capture-template'." + "Get the value for KEYWORD from the `org-roam-capture-template'." (plist-get (plist-get org-capture-plist :org-roam) keyword)) (defun org-roam-capture--put (&rest stuff) - "Puts properties from STUFF into the `org-roam-capture-template'." + "Put properties from STUFF into the `org-roam-capture-template'." (let ((p (plist-get org-capture-plist :org-roam))) (while stuff - (setq p (plist-put p - (pop stuff) (pop stuff)))) + (setq p (plist-put p (pop stuff) (pop stuff)))) (setq org-capture-plist (plist-put org-capture-plist :org-roam p)))) -(defun org-roam-capture--in-process-p () - "Return non-nil if a `org-roam-capture' buffer exists." - (cl-some (lambda (buffer) - (and (eq (buffer-local-value 'major-mode buffer) - 'org-mode) - (plist-get (buffer-local-value 'org-capture-current-plist buffer) - :org-roam))) - (buffer-list))) +;; FIXME: Pending upstream patch +;; https://orgmode.org/list/87h7tv9pkm.fsf@hidden/T/#u +;; +;; Org-capture's behaviour right now is that `org-capture-plist' is valid only +;; during the initialization of the Org-capture buffer. The value of +;; `org-capture-plist' is saved into buffer-local `org-capture-current-plist'. +;; However, the value for that particular capture is no longer accessible for +;; hooks in `org-capture-after-finalize-hook', since the capture buffer has been +;; cleaned up. +;; +;; This advice restores the global `org-capture-plist' during finalization, so +;; the plist is valid during both initialization and finalization of the +;; capture. +(defun org-roam-capture--update-plist (&optional _) + "Update global plist from local var." + (setq org-capture-plist org-capture-current-plist)) + +(advice-add 'org-capture-finalize :before #'org-roam-capture--update-plist) + +(defun org-roam-capture--finalize () + "Finalize the `org-roam-capture' process." + (unless org-note-abort + (pcase (org-roam-capture--get :finalize) + ('find-file + (when-let ((file-path (org-roam-capture--get :file-path))) + (org-roam--find-file file-path) + (run-hooks 'org-roam-capture-after-find-file-hook))) + ('insert-link + (when-let* ((mkr (org-roam-capture--get :insert-at)) + (buf (marker-buffer mkr))) + (with-current-buffer buf + (when-let ((region (org-roam-capture--get :region))) ;; Remove previously selected text. + (delete-region (car region) (cdr region))) + (let ((path (org-roam-capture--get :file-path)) + (desc (org-roam-capture--get :link-description))) + (if (eq (point) (marker-position mkr)) + (insert (org-roam--format-link path desc)) + (org-with-point-at mkr + (insert (org-roam--format-link path desc)))))))))) + (org-roam-capture--save-file-maybe) + (remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize)) + +(defun org-roam-capture--install-finalize () + "Install `org-roam-capture--finalize' if the capture is an Org-roam capture." + (when (org-roam-capture-p) + (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize))) + +(add-hook 'org-capture-prepare-finalize-hook #'org-roam-capture--install-finalize) (defun org-roam-capture--fill-template (str) - "Expands the template STR, returning the string. + "Expand the template STR, returning the string. This is an extension of org-capture's template expansion. First, it expands ${var} occurrences in STR, using `org-roam-capture--info'. @@ -320,19 +366,7 @@ Next, it expands the remaining template string using val))) nil) (org-capture-fill-template))) -(defun org-roam-capture--insert-link-h () - "Insert the link into the original buffer, after the capture process is done. -This is added as a hook to `org-capture-after-finalize-hook'." - (when (and (not org-note-abort) - (eq (org-roam-capture--get :capture-fn) - 'org-roam-insert)) - (when-let ((region (org-roam-capture--get :region))) ;; Remove previously selected text. - (delete-region (car region) (cdr region))) - (insert (org-roam--format-link (org-roam-capture--get :file-path) - (org-roam-capture--get :link-description)))) - (remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--insert-link-h)) - -(defun org-roam-capture--save-file-maybe-h () +(defun org-roam-capture--save-file-maybe () "Save the file conditionally. The file is saved if the original value of :no-save is not t and `org-note-abort' is not t. It is added to @@ -346,8 +380,7 @@ The file is saved if the original value of :no-save is not t and ((and (not (org-roam-capture--get :orig-no-save)) (not org-note-abort)) (with-current-buffer (org-capture-get :buffer) - (save-buffer)))) - (remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h)) + (save-buffer))))) (defun org-roam-capture--new-file () "Return the path to the new file during an Org-roam capture. @@ -367,7 +400,7 @@ aborted, we do the following: 2. Set the capture template's :no-save to t. -3. Add a function on `org-capture-after-finalize-hook' that saves +3. Add a function on `org-capture-before-finalize-hook' that saves the file if the original value of :no-save is not t and `org-note-abort' is not t." (let* ((name-templ (or (org-roam-capture--get :file-name) @@ -455,16 +488,6 @@ This function is used solely in Org-roam's capture templates: see (append converted options `(:org-roam ,org-roam-plist)))) (_ (user-error "Invalid capture template format: %s" template)))) -(defun org-roam-capture--find-file-h () - "Opens the newly created template file. -This is added as a hook to `org-capture-after-finalize-hook'. -Run the hooks defined in `org-roam-capture-after-find-file-hook'." - (unless org-note-abort - (when-let ((file-path (org-roam-capture--get :file-path))) - (org-roam--find-file file-path)) - (run-hooks 'org-roam-capture-after-find-file-hook)) - (remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h)) - (defcustom org-roam-capture-after-find-file-hook nil "Hook that is run right after an Org-roam capture process is finalized. Suitable for moving point." @@ -486,7 +509,6 @@ GOTO and KEYS argument have the same functionality as org-capture-templates-contexts) (when one-template-p (setq keys (caar org-capture-templates))) - (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h) (if (or one-template-p (eq org-roam-capture-function 'org-capture)) (org-capture goto keys) @@ -498,8 +520,6 @@ GOTO and KEYS argument have the same functionality as This uses the templates defined at `org-roam-capture-templates'." (interactive) (unless org-roam-mode (org-roam-mode)) - (when (org-roam-capture--in-process-p) - (user-error "Nested Org-roam capture processes not supported")) (let* ((completions (org-roam--get-title-path-completions)) (title-with-keys (org-roam-completion--completing-read "File: " completions)) @@ -510,7 +530,6 @@ This uses the templates defined at `org-roam-capture-templates'." (cons 'slug (funcall org-roam-title-to-slug-function title)) (cons 'file file-path))) (org-roam-capture--context 'capture)) - (setq org-roam-capture-additional-template-props (list :capture-fn 'org-roam-capture)) (condition-case err (org-roam-capture--capture) (error (user-error "%s. Please adjust `org-roam-capture-templates'" diff --git a/org-roam-dailies.el b/org-roam-dailies.el index 9eda04d..0ac341f 100644 --- a/org-roam-dailies.el +++ b/org-roam-dailies.el @@ -103,7 +103,7 @@ Template string :\n%v") (let ((org-roam-capture-templates org-roam-dailies-capture-templates) (org-roam-capture--info (list (cons 'time time))) (org-roam-capture--context 'dailies)) - (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h) + (setq org-roam-capture-additional-template-props (list :finalize 'find-file)) (org-roam--with-template-error 'org-roam-dailies-capture-templates (org-roam-capture--capture)))) diff --git a/org-roam.el b/org-roam.el index c770ae6..f2d6f39 100644 --- a/org-roam.el +++ b/org-roam.el @@ -1356,7 +1356,6 @@ which takes as its argument an alist of path-completions. See `org-roam--get-title-path-completions' for details." (interactive) (unless org-roam-mode (org-roam-mode)) - (when (org-roam-capture--in-process-p) (user-error "Org-roam capture in process")) (let* ((completions (funcall (or filter-fn #'identity) (or completions (org-roam--get-title-path-completions)))) (title-with-tags (org-roam-completion--completing-read "File: " completions @@ -1368,7 +1367,7 @@ which takes as its argument an alist of path-completions. See (let ((org-roam-capture--info `((title . ,title-with-tags) (slug . ,(funcall org-roam-title-to-slug-function title-with-tags)))) (org-roam-capture--context 'title)) - (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h) + (setq org-roam-capture-additional-template-props (list :finalize 'find-file)) (org-roam--with-template-error 'org-roam-capture-templates (org-roam-capture--capture)))))) @@ -1445,15 +1444,13 @@ If DESCRIPTION is provided, use this as the link label. See (when region ;; Remove previously selected text. (delete-region (car region) (cdr region))) (insert (org-roam--format-link target-file-path link-description))) - (when (org-roam-capture--in-process-p) - (user-error "Nested Org-roam capture processes not supported")) (let ((org-roam-capture--info `((title . ,title-with-tags) (slug . ,(funcall org-roam-title-to-slug-function title-with-tags)))) (org-roam-capture--context 'title)) - (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--insert-link-h) (setq org-roam-capture-additional-template-props (list :region region + :insert-at (point-marker) :link-description link-description - :capture-fn 'org-roam-insert)) + :finalize 'insert-link)) (org-roam--with-template-error 'org-roam-capture-templates (org-roam-capture--capture)))) res))