Ihor Radchenko <[email protected]> writes:
> It has been a month.
> Christian, any thoughts?

Yes, after taking some time to wrap my head around this. Thanks for the
ping. I attach a new patch for discussion, taking into consideration
your suggestions.

> Note that a block may contain multiple elements. Those elements may or
> may not be tables.

> Let's think about it more generally. What if we talk about something
> like drawer rather than dynamic block. What would be the most sensible
> way to include drawer contents?

This patch supports multiple elements, and drawers as well as dblocks.

> What we can do, is allowing
> block_name[] -> full contents
Implemented.

> block_name[0] -> first element
> block_name[0:2] -> first two elements
> for first two rows of table inside block -> block_name[0,-2:-1]
      ^^^^^
      last

Implemented.

I have included the default case

  block_name -> first element (equal to block_name[0])

This is to simplify things for people accessing the basic built-in
dblock types (colview, clocktable), which only have one element.
However, unlike my previous patch, they still have to add 0 as the first
dimension if they /want/ to index into the clocktable via the variable
reference, the clocktable will have a 3D index. We might want to make it
more intuitive still by making the value an element if there's only one,
and a list if there are several, so that indexing into the clocktable
with a 2D index works as one might expect. (But I only thought of that
after preparing everything for this mail, so I leave that for another
iteration.) WDYT?

> I think `org-babel-ref-index-list' should be able to handle arbitrary
> nesting.

It does! 

About whether or not to update the referenced dblock:

>> Maybe block_name(:eval yes), block_name(:eval no),
>> block_name(:eval auto) == block_name

I'm not sure it's a good idea to use the :eval header. What about
wanting a buffer-wide :eval setting, but wanting src blocks and dblocks
to behave differently?

In the patch I've introduced an :update pseudo-header ('pseudo' because
dynamic blocks don't have babel headers) and code to update the dblock
if it is present, regardless of value. But we may want to discuss these
choices further. Maybe we need a more specific name (:update-dblock?),
or a choice of yes|no|auto values as you suggested.

> As another data point, :var x=heading-id will return all the text past
> metadata (`org-babel-ref-headline-body').

While we're at it, we might want to consider whether it should also read
the metadata (and we could do all of the above in one big
org-babel-ref-greater-element function). :) OTOH, people can get the
metadata via a dblock, which they can now access with this patch.

Regards,
Christian

>From e972a1b497a819559c31f6023490d041c4c31895 Mon Sep 17 00:00:00 2001
From: Christian Moe <[email protected]>
Date: Mon, 4 May 2026 12:17:52 +0200
Subject: [PATCH] ob-ref.el: Support Babel refs to dblocks, drawers

* lisp/ob-ref.el (org-babel-ref-resolve): Add handling for dynamic
blocks and drawers.
(org-babel-ref-dblock): Add a function to read
referenced dynamic blocks and drawers either as full text or as
indexed elements.  Update dynamic blocks first if requested.

* doc/org-manual.org (Passing arguments): Mention support for dynamic
blocks and drawers, with examples.

This adds support for reading a named drawer or a dynamic block like a
clocktable or columnview via the `:var' header.  Previously,
references to such blocks failed with "Reference not found".  See the
linked thread for discussion.

Reported-by: Reza Housseini <[email protected]>
Link: https://list.orgmode.org/[email protected]
---
 doc/org-manual.org | 54 ++++++++++++++++++++++++++++++++++++++++++++++
 lisp/ob-ref.el     | 35 ++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index baa1127a0..1605592c9 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -18845,6 +18845,60 @@ a colon, for example: =:var table=other-file.org:example-table=.
   : on two lines for you.
   #+end_example
 
+- dynamic block, or drawer ::
+
+  A dynamic block, such as a columnview or a clocktable, or a drawer,
+  named with a =NAME= keyword, optionally followed by brackets.  With
+  an =:update= header, the dynamic block is updated before the
+  contents are read, changing the buffer.  The content of the block is
+  read depending on whether it is written as e.g. a table or a list,
+  as described in the examples above.  If the block contains several
+  elements (e.g. a table and a list), the variable is a list that can
+  be indexed (see below), with the number of the element as the first
+  dimension of the index.  Without indexing, the first element is
+  returned by default.  An empty index bracket returns the full text
+  content of the block verbatim.
+
+#+begin_example
+  ,#+NAME: dblock-example
+  ,#+BEGIN: columnview :hlines 1 :id local
+   | ITEM    | SCHEDULED        |
+   |---------+------------------|
+   | My task | [2026-05-03 Sun] |
+  ,#+END:
+
+  ,#+NAME: read-dblock-example
+  ,#+BEGIN_SRC emacs-lisp :var x=dblock-example[:update]()
+     x
+  ,#+END_SRC
+
+  ,#+RESULTS: read-dblock-example
+   | My task | [2026-05-03 Sun] |
+
+  ,#+NAME: read-dblock-example-fancy
+  ,#+BEGIN_SRC emacs-lisp :var x=dblock-example()[0,2,0:-1]
+     (format "%s is due on %s" (car x) (cadr x))
+  ,#+END_SRC
+
+  ,#+RESULTS: read-dblock-example-fancy
+   : My task is due on [2026-05-03 Sun]
+
+  ,#+NAME: drawer-example
+  ,:DETAILS:
+   This drawer contains a paragraph, a table, and a list.
+   | 1 | 2 |
+   - a
+   - b
+  ,:END:
+
+  ,#+BEGIN_SRC emacs-lisp :var x=drawer-example[2,1]
+     (format "The second item in the list is %s" x)
+  ,#+END_SRC
+
+  ,#+RESULTS:
+   : The second item in the list is b
+#+end_example
+
 Indexing variable values enables referencing portions of a variable.
 Indexes are 0 based with negative values counting backwards from the
 end.  If an index is separated by commas then each subsequent section
diff --git a/lisp/ob-ref.el b/lisp/ob-ref.el
index 8ce1332ba..5905f2965 100644
--- a/lisp/ob-ref.el
+++ b/lisp/ob-ref.el
@@ -69,6 +69,11 @@
 (declare-function org-in-commented-heading-p "org" (&optional no-inheritance element))
 (declare-function org-narrow-to-subtree "org" (&optional element))
 (declare-function org-fold-show-context "org-fold" (&optional key))
+(declare-function org-forward-element "org" nil)
+(declare-function org-element-end "org-element" (node))
+(declare-function org-element-contents-end "org-element" (node))
+(declare-function org-element-contents-begin "org-element" (node))
+(declare-function org-update-dblock "org" nil)
 
 (defun org-babel-ref-parse (assignment)
   "Parse a variable ASSIGNMENT in a header argument.
@@ -118,6 +123,31 @@ Emacs Lisp representation of the value of the variable."
 		     (point))
      (point-max))))
 
+(defun org-babel-ref-dblock (contents params)
+  "Return a value from the referenced drawer or dynamic block at point.
+
+If CONTENTS are non-nil, return the text content. Else return a list of
+the elements in the dynamic block. When PARAMS include `:update',
+update the dynamic block first."
+  (when (assoc :update params)
+    (org-update-dblock))
+  (let* ((d (org-element-at-point))
+         (beg (org-element-contents-begin d))
+         (end (org-element-contents-end   d))
+         elements)
+    (if contents
+        (buffer-substring-no-properties beg end)
+      (save-excursion
+        (goto-char beg)
+        (while (<= (org-element-end (org-element-at-point))
+                   end)
+          (setq elements
+                (cons (org-babel-read-element
+                       (org-element-at-point))
+                      elements))
+          (org-forward-element))
+        (reverse elements)))))
+
 (defvar org-babel-library-of-babel)
 (defun org-babel-ref-resolve (ref)
   "Resolve the reference REF and return its value."
@@ -184,6 +214,11 @@ Emacs Lisp representation of the value of the variable."
 				       (org-babel-execute-src-block
 					nil nil
                                         params)))
+                               ((or `dynamic-block `drawer)
+                                (unless (or contents index) ; Default to first element
+                                  (setq index "0"))
+                                (throw :found
+                                       (org-babel-ref-dblock contents params)))
 			       ((and (let v (org-babel-read-element e))
 				     (guard v))
 				(throw :found v))
-- 
2.43.0

Reply via email to