branch: externals/org commit 10ffea6b63fd3993558614789d9ab380f67b3c89 Author: Jack Kamm <jackk...@gmail.com> Commit: Jack Kamm <jackk...@gmail.com>
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 f0a2c0f580..fb3808b304 100644 --- a/lisp/ob-comint.el +++ b/lisp/ob-comint.el @@ -36,8 +36,13 @@ (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 @@ STRING contains the output originally inserted into the comint buffer." (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 @@ STRING contains the output originally inserted into the comint buffer." 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 @@ STRING contains the output originally inserted into the comint buffer." ;; 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 8e17af8cf6..fec90c45fa 100644 --- a/lisp/org-element.el +++ b/lisp/org-element.el @@ -3697,6 +3697,9 @@ Assume point is at the beginning of the 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 @@ Assume point is at the beginning of the inline source block." (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 a435457c4b..415f877aca 100644 --- a/testing/lisp/test-ob-python.el +++ b/testing/lisp/test-ob-python.el @@ -246,6 +246,37 @@ print('Yep!') (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.