0

If I do M-x org-refile on qux using the first (commented out) version of org-refile-targets, it brings up Foo, as expected. Using the second version, I get "No refile targets", whereas I would like Bar. Is it too far fetched to ask emacs to do that, or there is a way?

init.el:

  (use-package org
    :config
    ;;   (setq org-refile-targets '((nil :tag . "refile"))) ; version 1
        (setq org-refile-targets '((nil :regexp . ".+\n:PROPERTIES:\n:refile:   true\n:END:"))) ; version 2
    )

debug.org:

* Foo                                                                :refile:

* Bar
:PROPERTIES:
:refile:   true
:END:

* Inbox
** qux

enter image description here

1
  • No, the regexp matches only the headline, not anything after it. C-h v org-refile-targets says "... - a cons cell (:regexp . "REGEXP") with a regular expression matching headlines that are refiling targets.... ". AFAIK, you cannot use a property to specify the target headlines: you'd have to do some preprocessing to find all of them and add them to org-refile-targets before you do org-refile.
    – NickD
    Commented Apr 26, 2023 at 1:43

2 Answers 2

0

Here's an implementation of the preprocessing idea from my comment:

#+begin_src elisp
  (defun my/refiling-headline-list ()
    (org-map-entries (lambda () (nth 4 (org-heading-components))) "refile=\"true\"" 'file))

  (defun my/org-refile-with-properties ()
    (interactive)
    (let ((org-refile-targets `((nil :regexp . ,(mapconcat #'identity (my/refiling-headline-list) "\\|")))))
      (org-refile)))
#+end_src

Then instead of M-x org-refile, you do M-x my/org-refile-with-properties.

We let-bind org-refile-targets around a call to org-refile, where we set the variable using the :regexp specification, with a regexp that consists of the headlines of all the sections that have a refile property that is equal to true. We construct that regexp by concatenating all such headlines with a \| alternation operator between the headlines (we have to double the backslash in the string).

The list of headlines with the given property is obtained by calling org-map-entries: it visits each headline in the file that matches the property and applies a function on each one, where in this case the function just returns the text of the headline. org-map-properties, like all mapping functions, returns a list of all the results.

See the doc string of org-map-entries for information about the function and its arguments: C-h f org-map-entries. More information and some examples are available in the section Using the Mapping API in the Org mode manual. You can also search this site for org-map-entries: there are many examples of its use.

In the example, calling my/refiling-headline-list would return the list ("Bar") since that's the only headline with the property. In general, it would return a list of all such headlines: ("Bar" "Baz" ...). In the latter case, the mapconcat call would return the string "Bar\\|Baz\\|..." with all the headline strings concatenated and separated by an alternation operator.

This implementation only works for the current file: the refile targets have to be in the same file as the item to be refiled; that limitation comes about by the very simple implementation of my/refiling-headline-list which only looks in the current file and by the nil file specification in the let-binding of org-refile-targets in my/org-refile-with-properties. It is quite possible to extend it so that the refile targets from multiple files are made available, but for now, that's left as an exercise for the interested reader.

7
  • IT worked. I expected a 3 instead of nth 4... I presume 'file stands for current file. Where would I find this?
    – user19777
    Commented Apr 26, 2023 at 3:48
  • See the last paragraph of the (edited) answer for the current-file limitation. C-h f org-map-entries describes the function and its arguments and the Using the Mapping API in the manual has a more extended description and some examples. In addition, I and others have used it a number of times in answers on Emacs SE, so searching the site for org-map-entries should provide more examples.
    – NickD
    Commented Apr 26, 2023 at 4:07
  • 1
    Do you mean you expected (nth 3 ...) rather than (nth 4 ...)? org-heading-components returns a list (level reduced-level todo priority headline tags) so the headline that I want to extract is the fifth element of the returned list. nth starts counting from 0, so (nth 4 ...) returns that fifth element (which is not starring Milla Jovovich or Bruce Willis...)
    – NickD
    Commented Apr 26, 2023 at 4:20
  • I tested this further, it won't work unless the heading whose property meets the requirement is unique. For instance in this set up, the refiled entry will come under the first Inbox. * bar\n** Inbox\n* foo\n** Inbox\n:PROPERTIES:\n:refile: true\n:END:\n. To discriminate among nodes with identical headings I was thinking about replacing refile: true with refile:[identifier] and prompting the identifier in place of the heading. But I haven't been able to pull it off.
    – user19777
    Commented Jul 9, 2023 at 4:33
  • 1
    Sorry - I didn't notice. Yes, that's basically what I had in mind except for the org-element-* usage which is probably the better way to go.
    – NickD
    Commented Jul 11, 2023 at 1:51
1

Based on my last comment, here's the first half of a possible answer. Speculative: the second half would consist in invoking org-refile using RFLOC.

#+begin_src elisp
(defun my/get-refile-value-begin ()
  ;; (interactive)
  (let ((tree (org-element-parse-buffer 'headline))
        (matches '()))
    (org-element-map tree 'headline
      (lambda (element)
        (when (org-element-property :REFILE element)
          (push (list (org-element-property :REFILE element) (org-element-property :begin element)) matches)))
      nil nil nil)
    matches)) ;; C-x C-e
#end_src

Notice this oddity: :refile is internally stored as :REFILE.

Update: this work for either of foo or bar using the sample buffer.

#+begin_src elisp
(defun my/org-refile ()
  (interactive)
  (let* ((rfloc-pairs (my/get-refile-value-begin))
         (rfloc-values (mapcar #'car rfloc-pairs))
         (the-value (completing-read "Select refile target: " rfloc-values nil t))
         (rfloc-match (assoc the-value rfloc-pairs)))
        (org-refile nil nil (list (car rfloc-match) (buffer-file-name) nil (car (cdr rfloc-match)))))) ;; C-x C-e
#end_src 

Buffer:

* bar
** Inbox
:PROPERTIES:
:refile: bar
:END:

* foo
** Inbox
:PROPERTIES:
:refile: foo
:END:

# C-x C-e after:
(my/get-refile-value-begin) 
# yields:
# (("foo" 1201) ("bar" 1153))

Envisaged workflow:

  1. org-capture RET key RET new entry
  2. my/org-refile RET foo|bar
  3. Manually rearrange new entry inside the chosen target

enter image description here

enter image description hereenter image description here

1
  • I didn't know about the upper-casing of property names, but it is documented in the doc string of org-element-headline-parser. I believe the reason is that all the standard properties that the parser itself adds to the returned parse tree (:begin, :end, :contents-begin etc) are in lower case, so upper-casing user-defined properties makes sure that they don't conflict with those.
    – NickD
    Commented Jul 11, 2023 at 2:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.