I added :session header argument for ob-lua.el library.
I attached the patch file in email attachment.

I also have written a test for ob-lua :session, but failed. Hope you can
point out where is wrong. Thanks.

Here it is:

#+begin_src emacs-lisp
(ert-deftest test-ob-lua/session-default ()
  (should
   (equal
    (string-join
     '("#+BEGIN_SRC lua :session \"*lua*\""
       "print(\"hello, world\")<point>"
       "#+END_SRC"
       ""
       "#+RESULTS:"
       ": "
       ": print(\"hello, world\")"
       ": "
       ": hello, world"
       ": >")
     "\n")
    (org-test-with-temp-text
        (string-join
         '("#+BEGIN_SRC lua :session \"*lua*\""
           "print(\"hello, world\")<point>"
           "#+END_SRC")
         "\n")
      (let ((org-babel-hash-show-time nil))
        (org-babel-execute-src-block))
      (buffer-substring-no-properties (point-min) (point-max))))))
#+end_src

But this single testing failed in two ways:

I tested this single test with steps:

1. first load the necessary macros and functions in tests.
   =[M-x load-file RET /path/to/org-mode/testing/org-test.el RET]=
3. put point on the ert unit test and press =[C-M-x]= to evaluate the ert test.
4. =[M-x ert RET test-ob-lua/session-default RET]=

Here is the *ert* buffer output:

#+begin_example
Selector: test-ob-lua/session-default
Passed:  0
Failed:  1 (1 unexpected)
Skipped: 0
Total:   1/1

Started at:   2025-12-19 17:46:05+0800
Finished.
Finished at:  2025-12-19 17:46:08+0800

F

F test-ob-lua/session-default
    (ert-test-failed
     ((should
       (equal
        (string-join
         '("#+BEGIN_SRC lua :session \"*lua*\"" "print(\"hello, 
world\")<point>" "#+END_SRC" ""
           "#+RESULTS:" ": " ": print(\"hello, world\")" ": " ": hello, world" 
": >")
         "\n")
        (org-test-with-temp-text (string-join ... "\n")
          (let ... ...) (buffer-substring-no-properties ... ...))))
      :form
      (equal
       "#+BEGIN_SRC lua :session \"*lua*\"\nprint(\"hello, 
world\")<point>\n#+END_SRC\n\n#+RESULTS:\n: \n: print(\"hello, world\")\n: \n: 
hello, world\n: >"
       "#+BEGIN_SRC lua :session \"*lua*\"\nprint(\"hello, 
world\")\n#+END_SRC\n\n#+RESULTS[833e9c971f68b6513b25586dae31dec28afd0af3]:\n: 
\n: print(\"hello, world\")\n: \n: hello, world\n: >\n")
      :value nil :explanation
      (arrays-of-different-length 132 168
                                  "#+BEGIN_SRC lua :session 
\"*lua*\"\nprint(\"hello, world\")<point>\n#+END_SRC\n\n#+RESULTS:\n: \n: 
print(\"hello, world\")\n: \n: hello, world\n: >"
                                  "#+BEGIN_SRC lua :session 
\"*lua*\"\nprint(\"hello, 
world\")\n#+END_SRC\n\n#+RESULTS[833e9c971f68b6513b25586dae31dec28afd0af3]:\n: 
\n: print(\"hello, world\")\n: \n: hello, world\n: >\n"
                                  first-mismatch-at 54)))


#+end_example

The other way is executing command:

$ make BTEST_RE="test-ob-lua/session-default" test-dirty

Here is the output:

#+begin_example
install -m 755 -d /var/folders/ym/f3v5_yk1279g5ls70x3hzblh0000gn/T//tmp-orgtest
TMPDIR=/var/folders/ym/f3v5_yk1279g5ls70x3hzblh0000gn/T//tmp-orgtest emacs  -Q 
-batch --eval '(setq vc-handled-backends nil org-startup-folded nil 
org-element-cache-persistent nil)'  --eval '(add-to-list `load-path (concat 
default-directory "lisp"))' --eval '(add-to-list `load-path (concat 
default-directory "testing"))'  -l org-batch-test-init --eval '(setq 
org-batch-test t org-babel-load-languages (quote ( (awk . t)  (C . t)  (fortran 
. t)  (maxima . t)  (lilypond . t)  (octave . t)  (perl . t)  (python . t)  
(java . t)  (sqlite . t)  (eshell . t)  (calc . t)  (emacs-lisp . t)  (shell . 
t)  (org . t))) org-test-select-re "test-ob-lua/session-default" )' -l 
org-loaddefs.el -l testing/org-test.el -l ert -l org -l ox -l ol  --eval 
'(org-test-run-batch-tests org-test-select-re)'
Finding ID locations (1/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/setupfile3.org
Finding ID locations (2/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/setupfile.org
Finding ID locations (3/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/property-inheritance.org
Finding ID locations (4/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/org-exp.org
Finding ID locations (5/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-sed-test.org
Finding ID locations (6/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-screen-test.org
Finding ID locations (7/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-octave-test.org
Finding ID locations (8/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-maxima-test.org
Finding ID locations (9/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-lilypond-test.org
Finding ID locations (10/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-lilypond-broken.org
Finding ID locations (11/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-header-arg-defaults.org
Finding ID locations (12/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-fortran-test.org
Finding ID locations (13/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-awk-test.org
Finding ID locations (14/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/ob-C-test.org
Finding ID locations (15/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/normal.org
Finding ID locations (16/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/no-heading.org
Finding ID locations (17/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/macro-templates.org
Finding ID locations (18/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/links.org
Finding ID locations (19/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/link-in-heading.org
Finding ID locations (20/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/include2.org
Finding ID locations (21/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/include.org
Finding ID locations (22/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/babel.org
Finding ID locations (23/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/babel-dangerous.org
Finding ID locations (24/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/attachments.org
Finding ID locations (25/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/agenda-search.org
Finding ID locations (26/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/agenda-file2.org
Finding ID locations (27/27 files): 
/Users/stardiviner/Code/Emacs/org-mode/testing/examples/agenda-file.org
27 files scanned, 13 files contains IDs, and 54 IDs found.
0 source block added to Library of Babel
selected tests: test-ob-lua/session-default
Running 0 tests (2025-12-19 17:57:37+0800, selector 
‘"test-ob-lua/session-default"’)

Ran 0 tests, 0 results as expected, 0 unexpected (2025-12-19 17:57:37+0800, 
0.000059 sec)

/Applications/Xcode.app/Contents/Developer/usr/bin/make cleantest
rm -fr /var/folders/ym/f3v5_yk1279g5ls70x3hzblh0000gn/T//tmp-orgtest || { \
          find /var/folders/ym/f3v5_yk1279g5ls70x3hzblh0000gn/T//tmp-orgtest 
-type d -exec chmod u+w {} + && \
          rm -fr /var/folders/ym/f3v5_yk1279g5ls70x3hzblh0000gn/T//tmp-orgtest 
; \
        }
#+end_example


From be88db97af74c35a6fc12b054831f4f9fcfd1bf7 Mon Sep 17 00:00:00 2001
From: stardiviner <[email protected]>
Date: Fri, 19 Dec 2025 17:48:15 +0800
Subject: [PATCH] ob-lua.el: Add :session support for Lua source block

* lisp/ob-lua.el (org-babel-execute:lua): Support :session header
argument to evaluate source block code in Lua session buffer.
(org-babel-prep-session:lua, org-babel-load-session:lua,
org-babel-lua-initiate-session): functions to support :session header
argument.
---
 etc/ORG-NEWS                |  4 ++
 lisp/ob-lua.el              | 79 +++++++++++++++++++++++++++++++++----
 testing/lisp/test-ob-lua.el | 27 ++++++++++++-
 3 files changed, 102 insertions(+), 8 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index f4ff44d29..c3f255dc8 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -271,6 +271,10 @@ executable can be configured via =org-ditaa-exec=.
 SVG output can now be generated; note, however, that this requires a
 ditaa version of at least 0.11.0.
 
+*** =ob-lua=: support =:session= header argument
+
+Lua source block now support =:session= header argument like other Babel languages.
+
 ** New and changed options
 
 # Changes dealing with changing default values of customizations,
diff --git a/lisp/ob-lua.el b/lisp/ob-lua.el
index 159229c22..db2ec4be1 100644
--- a/lisp/ob-lua.el
+++ b/lisp/ob-lua.el
@@ -74,23 +74,47 @@ (defcustom org-babel-lua-multiple-values-separator ", "
   :package-version '(Org . "9.7")
   :type 'string)
 
+(defvar org-babel-lua-last-input-start nil)
+(defvar org-babel-lua-last-input-end nil)
+(defvar org-babel-lua-last-output-start nil)
+(defvar org-babel-lua-last-output-end nil)
+
 (defun org-babel-execute:lua (body params)
   "Execute Lua BODY according to PARAMS.
 This function is called by `org-babel-execute-src-block'."
-  (let* ((session (cdr (assq :session params)))
+  (let* ((session (org-babel-lua-initiate-session (cdr (assq :session params))))
          (result-params (cdr (assq :result-params params)))
          (result-type (cdr (assq :result-type params)))
 	 (return-val (when (eq result-type 'value)
 		       (cdr (assq :return params))))
 	 (preamble (cdr (assq :preamble params)))
          (full-body
 	  (org-babel-expand-body:generic
 	   (concat body (if return-val (format "\nreturn %s" return-val) ""))
 	   params (org-babel-variable-assignments:lua params)))
-         (result (org-babel-lua-evaluate
-		  full-body result-type result-params preamble)))
-    (when (and session (not (equal session "none")))
-      (user-error "Sessions not supported for Lua"))
+         (result (if session
+                     (with-current-buffer session
+                       ;; reset positions in a new evaluation
+                       (setq-local org-babel-lua-last-input-start (point-max)
+                                   org-babel-lua-last-input-end (point-max)
+                                   org-babel-lua-last-output-start (point-max)
+                                   org-babel-lua-last-output-end (point-max))
+                       ;; send source block code to session buffer
+                       (dolist (line (split-string full-body "\n"))
+	                     (goto-char (point-max))
+	                     (insert line)
+                         (setq-local org-babel-lua-last-input-end (point-max))
+                         (comint-accumulate))
+                       (comint-send-input nil t)
+                       ;; get evaluation result output
+                       (sit-for 1.0)
+                       (when (null comint-process-echoes)
+                         (setq-local org-babel-lua-last-output-start (1+ org-babel-lua-last-input-end))
+                         (setq-local org-babel-lua-last-output-end (- (point-max) 3))
+                         (buffer-substring-no-properties
+                          org-babel-lua-last-output-start org-babel-lua-last-output-end)))
+                   (org-babel-lua-evaluate
+		    full-body result-type result-params preamble))))
     (org-babel-reassemble-table
      result
      (org-babel-pick-name (cdr (assq :colname-names params))
@@ -142,8 +166,6 @@ (defun org-babel-lua-table-or-string (results)
                 res)
       res)))
 
-(defvar org-babel-lua-buffers '((:default . "*Lua*")))
-
 (defvar org-babel-lua-eoe-indicator "--eoe"
   "A string to indicate that evaluation has completed.")
 
@@ -249,6 +271,49 @@ (defun org-babel-lua-evaluate
       raw
       (org-babel-lua-table-or-string (org-trim raw)))))
 
+;; :session support
+
+(defvar org-babel-lua-default-buffer "*lua*")
+
+(defun org-babel-lua-session-live-p (session)
+  "Non-nil if Lua SESSION exists."
+  (get-buffer session))
+
+(defun org-babel-prep-session:lua (session params)
+  "Prepare SESSION according to the header arguments in PARAMS."
+  (let* ((session (org-babel-gnuplot-initiate-session session))
+         (var-lines (org-babel-variable-assignments:lua params)))
+    (org-babel-comint-in-buffer session
+      (dolist (var-line var-lines)
+        (insert var-line)
+        (comint-send-input nil t)
+        (org-babel-comint-wait-for-output session)
+        (sit-for .1)
+        (goto-char (point-max))))
+    session))
+
+(defun org-babel-load-session:lua (session body params)
+  "Load BODY into SESSION."
+  (save-window-excursion
+    (let ((buffer (org-babel-prep-session:lua session params)))
+      (with-current-buffer buffer
+        (goto-char (process-mark (get-buffer-process (current-buffer))))
+        ;; (goto-char (point-max))
+        (insert (org-babel-chomp body)))
+      buffer)))
+
+(defun org-babel-lua-initiate-session (&optional session _params)
+  "Initiate a Lua session.
+If there is not a current inferior-process-buffer in SESSION
+then create one.  Return the initialized session.  The current
+`lua-mode' doesn't provide support for multiple sessions."
+  (org-require-package 'lua-mode)
+  (unless (string= session "none")
+    (unless (org-babel-lua-session-live-p session)
+      (run-lua (or session org-babel-lua-default-buffer) "lua"))
+    (get-buffer session)))
+
+
 (provide 'ob-lua)
 
 ;;; ob-lua.el ends here
diff --git a/testing/lisp/test-ob-lua.el b/testing/lisp/test-ob-lua.el
index 2e19c4a3d..df0b0e66e 100644
--- a/testing/lisp/test-ob-lua.el
+++ b/testing/lisp/test-ob-lua.el
@@ -417,9 +417,34 @@ (ert-deftest test-ob-lua/no-sessions ()
     '(user-error "Sessions not supported for Lua")
     (should-error
      (org-test-with-temp-text "src_lua[:session 1]{}"
-       (org-babel-execute-src-block))
+                              (org-babel-execute-src-block))
      :type 'user-error))))
 
+(ert-deftest test-ob-lua/session-default ()
+  (should
+   (equal
+    (string-join
+     '("#+BEGIN_SRC lua :session \"*lua*\""
+       "print(\"hello, world\")<point>"
+       "#+END_SRC"
+       ""
+       "#+RESULTS:"
+       ": "
+       ": print(\"hello, world\")"
+       ": "
+       ": hello, world"
+       ": >")
+     "\n")
+    (org-test-with-temp-text
+        (string-join
+         '("#+BEGIN_SRC lua :session \"*lua*\""
+           "print(\"hello, world\")<point>"
+           "#+END_SRC")
+         "\n")
+      (let ((org-babel-hash-show-time nil))
+        (org-babel-execute-src-block))
+      (buffer-substring-no-properties (point-min) (point-max))))))
+
 (provide 'test-ob-lua)
 
 ;;; test-ob-lua.el ends here
-- 
2.50.1 (Apple Git-155)


-- 

[ stardiviner ]
I try to make every word tell the meaning that I want to express without 
misunderstanding.

Blog: https://stardiviner.github.io/
IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner
GPG: F09F650D7D674819892591401B5DF1C95AE89AC3

Attachment: signature.asc
Description: PGP signature

Reply via email to