From 66e86734cb5cb36b10c838ebab7b3400ef9372f8 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Wed, 13 May 2026 13:14:24 -0400 Subject: [PATCH] literate: add with-terminal, suspend-backend, resume-backend to org source with-terminal macro was only in tangled .lisp (not .org). suspend-backend and resume-backend generics + simple-backend methods + tests were also in hand-edited .lisp only. All three added to org/backend-protocol.org with proper prose, following the literate programming discipline. Also added suspend/resume assertions to simple-backend-lifecycle test suite. --- org/backend-protocol.org | 98 +++++++++++++++++++++++++++++++++++++++- src/backend/classes.lisp | 86 ++++++++++++++++++----------------- src/backend/package.lisp | 6 +-- src/backend/tests.lisp | 4 +- 4 files changed, 146 insertions(+), 48 deletions(-) diff --git a/org/backend-protocol.org b/org/backend-protocol.org index 149e60c..98e737c 100644 --- a/org/backend-protocol.org +++ b/org/backend-protocol.org @@ -160,6 +160,8 @@ simple backend. (is (typep b 'simple-backend)) (initialize-backend b) (is-false (capable-p b :truecolor) "simple backend has no truecolor") + (is (null (multiple-value-list (suspend-backend b)))) + (is (null (multiple-value-list (resume-backend b)))) (shutdown-backend b))) #+END_SRC @@ -401,6 +403,7 @@ construction without actually rendering to a terminal. #:backend #:simple-backend ;; Lifecycle #:initialize-backend #:shutdown-backend + #:suspend-backend #:resume-backend #:backend-size #:backend-write #:backend-clear ;; Drawing #:draw-text #:draw-border #:draw-rect @@ -414,8 +417,9 @@ construction without actually rendering to a terminal. ;; Queries #:capable-p ;; Constructors - #:make-simple-backend - ;; Modern backend + #:make-simple-backend + #:with-terminal + ;; Modern backend #:modern-backend #:make-modern-backend ;; Detection #:detect-backend #:*detected-backend* @@ -664,6 +668,78 @@ keywords include ~:truecolor~, ~:osc8~, ~:sync~, ~:mouse~, nil)) #+END_SRC +*** Suspend and Resume + +Temporary terminal suspension and re-initialization. Used when the +application receives SIGTSTP (suspend) or SIGCONT (resume) signals. +The default methods are no-ops; backends with terminal state override +these to restore cooked mode on suspend and raw mode on resume. + +#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp +(in-package :cl-tty.backend) + +(defgeneric suspend-backend (backend) + (:documentation "Temporarily suspend the backend, restoring terminal to normal state. +Called before SIGTSTP or similar suspension. Application should redraw after resume.") + (:method ((b backend)) (values))) + +(defgeneric resume-backend (backend) + (:documentation "Re-initialize the backend after suspension. +Called after SIGCONT or similar resume. Re-enables raw mode and backend features.") + (:method ((b backend)) (values))) +#+END_SRC + +*** With Terminal + +A convenience macro that initializes a terminal backend, executes body, +and guarantees cleanup on exit via ~unwind-protect~. + +The macro detects a suitable backend, initializes it, captures the +terminal dimensions, binds them to the provided variables, executes the +body, and always calls ~shutdown-backend~ when the body exits (whether +normally or by a non-local control transfer). + +Arguments: +- ~backend-var~ — bound to the detected backend instance. +- ~cols-var~, ~rows-var~ (optional) — bound to terminal columns and + lines captured after initialization. +- ~&body body~ — executed with the above bindings. + +#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp +(in-package :cl-tty.backend) + +(defmacro with-terminal ((backend-var &optional cols-var rows-var) + &body body) + "Execute BODY with a fully initialized terminal backend. + +DETECT-BACKEND, INITIALIZE-BACKEND, and SHUTDOWN-BACKEND are called +automatically. The backend instance is bound to BACKEND-VAR. If +COLS-VAR and ROWS-VAR are provided, they are bound to the terminal +dimensions at startup. + +The caller should wrap this in SB-POSIX:WITH-RAW-TERMINAL (or +equivalent) if raw-mode input handling is needed. + +Example: + (with-terminal (be cols rows) + (loop for ev = (read-event be :timeout 0.1) + while ev + do (format t \"~A~%\" ev))))" + (let ((be-sym (gensym "BE")) + (c-sym (gensym "COLS")) + (r-sym (gensym "ROWS"))) + `(let* ((,be-sym (detect-backend)) + ,@(when cols-var `((,c-sym (nth-value 0 (backend-size ,be-sym))))) + ,@(when rows-var `((,r-sym (nth-value 1 (backend-size ,be-sym)))))) + (initialize-backend ,be-sym) + (unwind-protect + (let ((,backend-var ,be-sym) + ,@(when cols-var `((,cols-var ,c-sym))) + ,@(when rows-var `((,rows-var ,r-sym)))) + ,@body) + (shutdown-backend ,be-sym))))) +#+END_SRC + ** Simple Backend ~simple-backend~ inherits from ~backend~ and implements every @@ -721,6 +797,24 @@ restore. Returns multiple values to satisfy the protocol contract. (values)) #+END_SRC +*** Suspend (simple-backend) + +No-op — simple backend has no terminal state to save. + +#+begin_src lisp :tangle ../src/backend/simple.lisp +(defmethod suspend-backend ((b simple-backend)) + (values)) +#+end_src + +*** Resume (simple-backend) + +No-op — simple backend has no terminal state to restore. + +#+begin_src lisp :tangle ../src/backend/simple.lisp +(defmethod resume-backend ((b simple-backend)) + (values)) +#+end_src + *** Backend Size (Simple) Returns hard-coded 80x24 dimensions. A real implementation would use diff --git a/src/backend/classes.lisp b/src/backend/classes.lisp index d103806..677a565 100644 --- a/src/backend/classes.lisp +++ b/src/backend/classes.lisp @@ -8,47 +8,6 @@ (defgeneric shutdown-backend (backend) (:method ((b backend)) (values))) -(defgeneric suspend-backend (backend) - (:documentation "Temporarily suspend the backend, restoring terminal to normal state. -Called before SIGTSTP or similar suspension. Application should redraw after resume.") - (:method ((b backend)) (values))) - -(defgeneric resume-backend (backend) - (:documentation "Re-initialize the backend after suspension. -Called after SIGCONT or similar resume. Re-enables raw mode and backend features.") - (:method ((b backend)) (values))) - -(defmacro with-terminal ((backend-var &optional cols-var rows-var) - &body body) - "Execute BODY with a fully initialized terminal backend. - -DETECT-BACKEND, INITIALIZE-BACKEND, and SHUTDOWN-BACKEND are called -automatically. The backend instance is bound to BACKEND-VAR. If -COLS-VAR and ROWS-VAR are provided, they are bound to the terminal -dimensions at startup. - -The caller should wrap this in SB-POSIX:WITH-RAW-TERMINAL (or -equivalent) if raw-mode input handling is needed. - -Example: - (with-terminal (be cols rows) - (loop for ev = (read-event be :timeout 0.1) - while ev - do (format t \"~A~%\" ev))))" - (let ((be-sym (gensym "BE")) - (c-sym (gensym "COLS")) - (r-sym (gensym "ROWS"))) - `(let* ((,be-sym (detect-backend)) - ,@(when cols-var `((,c-sym (nth-value 0 (backend-size ,be-sym))))) - ,@(when rows-var `((,r-sym (nth-value 1 (backend-size ,be-sym)))))) - (initialize-backend ,be-sym) - (unwind-protect - (let ((,backend-var ,be-sym) - ,@(when cols-var `((,cols-var ,c-sym))) - ,@(when rows-var `((,rows-var ,r-sym)))) - ,@body) - (shutdown-backend ,be-sym))))) - (defgeneric backend-size (backend) (:method ((b backend)) (values 80 24))) @@ -103,3 +62,48 @@ Example: (:method ((b backend) feature) (declare (ignore feature)) nil)) + +(in-package :cl-tty.backend) + +(defgeneric suspend-backend (backend) + (:documentation "Temporarily suspend the backend, restoring terminal to normal state. +Called before SIGTSTP or similar suspension. Application should redraw after resume.") + (:method ((b backend)) (values))) + +(defgeneric resume-backend (backend) + (:documentation "Re-initialize the backend after suspension. +Called after SIGCONT or similar resume. Re-enables raw mode and backend features.") + (:method ((b backend)) (values))) + +(in-package :cl-tty.backend) + +(defmacro with-terminal ((backend-var &optional cols-var rows-var) + &body body) + "Execute BODY with a fully initialized terminal backend. + +DETECT-BACKEND, INITIALIZE-BACKEND, and SHUTDOWN-BACKEND are called +automatically. The backend instance is bound to BACKEND-VAR. If +COLS-VAR and ROWS-VAR are provided, they are bound to the terminal +dimensions at startup. + +The caller should wrap this in SB-POSIX:WITH-RAW-TERMINAL (or +equivalent) if raw-mode input handling is needed. + +Example: + (with-terminal (be cols rows) + (loop for ev = (read-event be :timeout 0.1) + while ev + do (format t \"~A~%\" ev))))" + (let ((be-sym (gensym "BE")) + (c-sym (gensym "COLS")) + (r-sym (gensym "ROWS"))) + `(let* ((,be-sym (detect-backend)) + ,@(when cols-var `((,c-sym (nth-value 0 (backend-size ,be-sym))))) + ,@(when rows-var `((,r-sym (nth-value 1 (backend-size ,be-sym)))))) + (initialize-backend ,be-sym) + (unwind-protect + (let ((,backend-var ,be-sym) + ,@(when cols-var `((,cols-var ,c-sym))) + ,@(when rows-var `((,rows-var ,r-sym)))) + ,@body) + (shutdown-backend ,be-sym))))) diff --git a/src/backend/package.lisp b/src/backend/package.lisp index fc7d2cd..7657d95 100644 --- a/src/backend/package.lisp +++ b/src/backend/package.lisp @@ -19,9 +19,9 @@ ;; Queries #:capable-p ;; Constructors - #:make-simple-backend - #:with-terminal - ;; Modern backend + #:make-simple-backend + #:with-terminal + ;; Modern backend #:modern-backend #:make-modern-backend ;; Detection #:detect-backend #:*detected-backend* diff --git a/src/backend/tests.lisp b/src/backend/tests.lisp index a509103..8e25684 100644 --- a/src/backend/tests.lisp +++ b/src/backend/tests.lisp @@ -24,6 +24,8 @@ (is (typep b 'simple-backend)) (initialize-backend b) (is-false (capable-p b :truecolor) "simple backend has no truecolor") + (is (null (multiple-value-list (suspend-backend b)))) + (is (null (multiple-value-list (resume-backend b)))) (shutdown-backend b))) (test simple-backend-draw-text @@ -103,8 +105,6 @@ (is (null (multiple-value-list (cursor-style b :block)))) (is (null (multiple-value-list (begin-sync b)))) (is (null (multiple-value-list (end-sync b)))) - (is (null (multiple-value-list (suspend-backend b)))) - (is (null (multiple-value-list (resume-backend b)))) (shutdown-backend b))) (test sync-is-noop-on-simple