diff --git a/org/text-input.org b/org/text-input.org index b1c6bae..10b2c8c 100644 --- a/org/text-input.org +++ b/org/text-input.org @@ -196,6 +196,7 @@ via ~sb-posix~ directly. #:with-raw-terminal ;; Event reading #:read-event + #:*terminal-resized-p* ;; UTF-8 input support #:utf8-decode ;; TextInput @@ -704,6 +705,12 @@ All the complexity lives in ~%read-event~ and its callees. #+BEGIN_SRC lisp :tangle ../src/components/input.lisp (defmethod read-event ((b cl-tty.backend:backend) &key timeout) (declare (ignore b)) + ;; Check for pending terminal resize before reading input. + ;; The SIGWINCH handler sets *terminal-resized-p* asynchronously. + (when *terminal-resized-p* + (setf *terminal-resized-p* nil) + (multiple-value-bind (w h) (backend-size b) + (return-from read-event (values :resize (cons w h))))) (when (probe-file "/dev/stdin") (%read-event :timeout timeout))) #+END_SRC @@ -2062,4 +2069,22 @@ The test suite is tangled to ~../tests/input-tests.lisp~ and covers: (remhash :local *keymaps*) (is-false (gethash :global *keymaps*)) (is-false (gethash :local *keymaps*))) + +(test resize-event-check + "read-event returns :resize when *terminal-resized-p* is set" + (let ((b (make-instance 'cl-tty.backend:backend))) + (setf cl-tty.input:*terminal-resized-p* t) + (multiple-value-bind (type data) (cl-tty.input:read-event b :timeout 0) + (is (eq :resize type)) + (is (consp data)) + (is (integerp (car data))) + (is (integerp (cdr data)))) + (is-false cl-tty.input:*terminal-resized-p*))) + +(test with-terminal-macro-expands + "with-terminal macro expands and compiles" + (is (macro-function 'cl-tty.backend:with-terminal)) + (let ((expanded (macroexpand-1 '(cl-tty.backend:with-terminal (be) + (print be))))) + (is (listp expanded)))) #+END_SRC diff --git a/src/backend/classes.lisp b/src/backend/classes.lisp index ff181e9..5c3e426 100644 --- a/src/backend/classes.lisp +++ b/src/backend/classes.lisp @@ -18,6 +18,37 @@ Called before SIGTSTP or similar suspension. Application should redraw after res 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 (gensym)) (rows-var (gensym))) + &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)) + (,c-sym (nth-value 0 (backend-size ,be-sym))) + (,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))) diff --git a/src/backend/package.lisp b/src/backend/package.lisp index de92077..fc7d2cd 100644 --- a/src/backend/package.lisp +++ b/src/backend/package.lisp @@ -20,6 +20,7 @@ #:capable-p ;; Constructors #:make-simple-backend + #:with-terminal ;; Modern backend #:modern-backend #:make-modern-backend ;; Detection diff --git a/src/components/input-package.lisp b/src/components/input-package.lisp index 3a312d2..2eff30e 100644 --- a/src/components/input-package.lisp +++ b/src/components/input-package.lisp @@ -15,6 +15,7 @@ #:with-raw-terminal ;; Event reading #:read-event + #:*terminal-resized-p* ;; UTF-8 input support #:utf8-decode ;; TextInput diff --git a/src/components/input.lisp b/src/components/input.lisp index 1569817..ba3c76c 100644 --- a/src/components/input.lisp +++ b/src/components/input.lisp @@ -187,5 +187,11 @@ (defmethod read-event ((b cl-tty.backend:backend) &key timeout) (declare (ignore b)) + ;; Check for pending terminal resize before reading input. + ;; The SIGWINCH handler sets *terminal-resized-p* asynchronously. + (when *terminal-resized-p* + (setf *terminal-resized-p* nil) + (multiple-value-bind (w h) (backend-size b) + (return-from read-event (values :resize (cons w h))))) (when (probe-file "/dev/stdin") (%read-event :timeout timeout))) diff --git a/tests/input-tests.lisp b/tests/input-tests.lisp index f8fc8dd..a5cf952 100644 --- a/tests/input-tests.lisp +++ b/tests/input-tests.lisp @@ -389,3 +389,21 @@ (remhash :local *keymaps*) (is-false (gethash :global *keymaps*)) (is-false (gethash :local *keymaps*))) + +(test resize-event-check + "read-event returns :resize when *terminal-resized-p* is set" + (let ((b (make-instance 'cl-tty.backend:backend))) + (setf cl-tty.input:*terminal-resized-p* t) + (multiple-value-bind (type data) (cl-tty.input:read-event b :timeout 0) + (is (eq :resize type)) + (is (consp data)) + (is (integerp (car data))) + (is (integerp (cdr data)))) + (is-false cl-tty.input:*terminal-resized-p*))) + +(test with-terminal-macro-expands + "with-terminal macro expands and compiles" + (is (macro-function 'cl-tty.backend:with-terminal)) + (let ((expanded (macroexpand-1 '(cl-tty.backend:with-terminal (be) + (print be))))) + (is (listp expanded))))