Ihor Radchenko <yanta...@posteo.net> writes:

> 1. Probably better make it internal function for more flexibility in
>    future
> 2. You need to consult `org-element-context' to make sure that we are
>    really at inline src block, but not inside some kind of verbatim
>    markup.
> 3. Rather than copying regexps around, please factor it out and create a
>    constant in org-element.el

I'm attaching an updated patch to fix the inline async session issue
that incorporates this feedback along with some other changes.

Rather than creating a general function in ob-core to find the previous
src or inline src block, I decided to create a private function in
ob-comint that is specialized for finding the block of an async result
(`org-babel-comint-async--find-src').  In addition to using
`org-element-context' to make sure we really are at a source block, I
also added a check that the block's result contains the hash
(`uuid-or-tmpfile') that the async result replaces.

I also created a constant for the regexp in org-element.el, and added
unit tests in test-ob-python.el for inline and async inline session
blocks.

>From 29fc7ae73b2d9968a39b36eae787d03ff004f56f Mon Sep 17 00:00:00 2001
From: Jack Kamm <jackk...@gmail.com>
Date: Sun, 16 Mar 2025 21:12:34 -0700
Subject: [PATCH] ob-comint: Fix async session evaluation for inline src blocks

* lisp/ob-comint.el: Imports for functions and variables from
org-element and org-element-ast.  Note org-element.el cannot be
directly imported due to recursive requires, so use declare-function
and defvar instead.
(org-babel-comint-async-filter): Use new function
`org-babel-comint-async--find-src' instead of
`org-babel-previous-src-block'.
(org-babel-comint-async--find-src): New helper function to find the
source block or inline source associated with an async session result.
* lisp/org-element.el (org-element-inline-src-block-regexp): New
constant regexp to match inline source blocks.
(org-element-inline-src-block-parser): Use
`org-element-inline-src-block-regexp'.
*
testing/lisp/test-ob-python.el (test-ob-python/inline-session-output):
New test for inline python session blocks.
(test-ob-python/async-inline-session-output): New test for async inline python
session blocks.
---
 lisp/ob-comint.el              | 46 +++++++++++++++++++++++++++++++---
 lisp/org-element.el            |  5 +++-
 testing/lisp/test-ob-python.el | 31 +++++++++++++++++++++++
 3 files changed, 77 insertions(+), 5 deletions(-)

diff --git a/lisp/ob-comint.el b/lisp/ob-comint.el
index f0a2c0f58..fb3808b30 100644
--- a/lisp/ob-comint.el
+++ b/lisp/ob-comint.el
@@ -36,8 +36,13 @@ (org-assert-version)
 
 (require 'ob-core)
 (require 'org-compat)
+(require 'org-element-ast)
+
 (require 'comint)
 
+(declare-function org-element-context "org-element" (&optional element))
+(defvar org-element-inline-src-block-regexp)
+
 (defun org-babel-comint-buffer-livep (buffer)
   "Check if BUFFER is a comint buffer with a live process."
   (let ((buffer (when buffer (get-buffer buffer))))
@@ -312,8 +317,7 @@ (defun org-babel-comint-async-filter (string)
 		          (with-current-buffer buf
 			    (save-excursion
 			      (goto-char (point-min))
-			      (when (search-forward tmp-file nil t)
-			        (org-babel-previous-src-block)
+			      (when (org-babel-comint-async--find-src tmp-file)
                                 (let* ((info (org-babel-get-src-block-info))
                                        (params (nth 2 info))
                                        (result-params
@@ -363,8 +367,7 @@ (defun org-babel-comint-async-filter (string)
 		       until (with-current-buffer buf
 			       (save-excursion
 			         (goto-char (point-min))
-			         (when (search-forward uuid nil t)
-				   (org-babel-previous-src-block)
+			         (when (org-babel-comint-async--find-src uuid)
                                    (let* ((info (org-babel-get-src-block-info))
                                           (params (nth 2 info))
                                           (result-params
@@ -376,6 +379,41 @@ (defun org-babel-comint-async-filter (string)
 	      ;; Remove uuid from the list to search for
 	      (setq uuid-list (delete uuid uuid-list)))))))))
 
+(defun org-babel-comint-async--find-src (uuid-or-tmpfile)
+  "Find source block associated with an async comint result.
+UUID-OR-TMPFILE is the uuid or tmpfile associated with the result.
+Returns non-nil if the source block is succesfully found, and moves
+point there.
+
+This function assumes that UUID-OR-TMPFILE was previously inserted as
+the source block's result, as a placeholder until the true result
+becomes ready.  It may fail to find the source block if the buffer was
+modified so that UUID-OR-TMPFILE is no longer the result of the source
+block, or if it has been copied elsewhere into the buffer (this is a
+limitation of the current async implementation)."
+  (goto-char (point-min))
+  (when (search-forward uuid-or-tmpfile nil t)
+    (let ((uuid-pos (point)))
+      (and (re-search-backward
+            ;; find the nearest preceding src or inline-src block
+            (rx (or (regexp org-babel-src-block-regexp)
+                    (regexp org-element-inline-src-block-regexp)))
+            nil t)
+           ;; check it's actually a src block and not verbatim text
+           (org-element-type-p (org-element-context)
+                               '(inline-src-block src-block))
+           ;; Check result contains the uuid. There isn't a simple way
+           ;; to extract the result value that works in all cases
+           ;; (e.g. inline blocks or results drawers), so instead
+           ;; check the result region contains the found uuid position
+           (let ((result-where (org-babel-where-is-src-block-result)))
+             (when result-where
+               (save-excursion
+                 (goto-char result-where)
+                 (and
+                  (>= uuid-pos (org-element-property :begin (org-element-context)))
+                  (< uuid-pos (org-element-property :end (org-element-context)))))))))))
+
 (defun org-babel-comint-async-register
     (session-buffer org-buffer indicator-regexp
 		    chunk-callback file-callback
diff --git a/lisp/org-element.el b/lisp/org-element.el
index 8e17af8cf..fec90c45f 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -3697,6 +3697,9 @@ (defun org-element-inline-babel-call-interpreter (inline-babel-call _)
 
 ;;;; Inline Src Block
 
+(defconst org-element-inline-src-block-regexp "\\<src_\\([^ \t\n[{]+\\)[{[]"
+  "Regexp matching inline source blocks.")
+
 (defun org-element-inline-src-block-parser ()
   "Parse inline source block at point, if any.
 
@@ -3709,7 +3712,7 @@ (defun org-element-inline-src-block-parser ()
   (save-excursion
     (catch :no-object
       (when (let ((case-fold-search nil))
-	      (looking-at "\\<src_\\([^ \t\n[{]+\\)[{[]"))
+	      (looking-at org-element-inline-src-block-regexp))
 	(goto-char (match-end 1))
 	(let ((begin (match-beginning 0))
 	      (language (org-element--get-cached-string
diff --git a/testing/lisp/test-ob-python.el b/testing/lisp/test-ob-python.el
index a435457c4..415f877ac 100644
--- a/testing/lisp/test-ob-python.el
+++ b/testing/lisp/test-ob-python.el
@@ -246,6 +246,37 @@ (ert-deftest test-ob-python/async-simple-session-output ()
 			        (goto-char (org-babel-where-is-src-block-result))
 			        (org-babel-read-result)))))))))
 
+(ert-deftest test-ob-python/inline-session-output ()
+  ;; Disable the test on older Emacs as built-in python.el sometimes
+  ;; fail to initialize session.
+  (skip-unless (version<= "28" emacs-version))
+  (let ((org-babel-temporary-directory temporary-file-directory)
+        (org-confirm-babel-evaluate nil)
+        (org-babel-inline-result-wrap "=%s="))
+    (org-test-with-temp-text
+     "src_python[:session :results output]{print(1+1)}"
+     (should (string= "2" (org-babel-execute-src-block))))))
+
+(ert-deftest test-ob-python/async-inline-session-output ()
+  ;; Disable the test on older Emacs as built-in python.el sometimes
+  ;; fail to initialize session.
+  (skip-unless (version<= "28" emacs-version))
+  (let ((org-babel-temporary-directory temporary-file-directory)
+        (org-confirm-babel-evaluate nil)
+        (org-babel-inline-result-wrap "=%s=")
+        (test-line "src_python[:session :async yes :results output]{print(1+1)}"))
+    (org-test-with-temp-text
+     test-line
+     (goto-char (point-min)) (org-babel-execute-maybe)
+     (should (let* ((expected-result "2")
+                    (expected-full (format "%s {{{results(=%s=)}}}"
+                                           test-line expected-result)))
+	       (and (not (string= expected-result (org-babel-execute-src-block)))
+		    (string= expected-full
+			     (progn
+			       (sleep-for 0.200)
+                               (buffer-substring-no-properties (point-at-bol) (point-at-eol))))))))))
+
 (ert-deftest test-ob-python/async-named-output ()
   ;; Disable the test on older Emacs as built-in python.el sometimes
   ;; fail to initialize session.
-- 
2.49.0

Reply via email to