From e453f9aad97aa5d77cb14101d758c421827e6cac Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Thu, 14 May 2026 15:57:37 -0400 Subject: [PATCH] fix: use global vars for cat subprocess to avoid let* scope crash Replaced (let* ((cat-proc ...) (tty-in ...)) ...) with global special variables *cat-proc* and *tty-in* with defvar declarations. The let* caused 'unbound variable' errors on Ctrl+Q because the lexical scope didn't extend to terminate-process. Global vars have indefinite scope and work reliably regardless of paren nesting. --- org/channel-tui-main.org | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/org/channel-tui-main.org b/org/channel-tui-main.org index 421316d..b3129c0 100644 --- a/org/channel-tui-main.org +++ b/org/channel-tui-main.org @@ -886,6 +886,9 @@ Event handlers + daemon I/O + main loop. (:down (lambda (e) (declare (ignore e)) (on-key :down))) (:escape (lambda (e) (declare (ignore e)) (on-key :escape))))) +(defvar *cat-proc* nil "Cat subprocess for keyboard input") +(defvar *tty-in* nil "Stream from cat subprocess stdout") + (defun tui-main () (init-state) (load-history) @@ -912,12 +915,14 @@ Event handlers + daemon I/O + main loop. ;; bytes through a pipe that SBCL reads reliably. ;; stty -icanon -echo is set by the bash script before exec sbcl. ;; A cat subprocess reads keyboard input from the terminal. - (let* ((cat-proc (uiop:launch-program '("cat") + ;; Global vars for cat subprocess — avoid lexical let* scope issue + (progn + (setq *cat-proc* (uiop:launch-program '("cat") :output :stream :input :interactive - :stderr nil)) - (tty-in (uiop:process-info-output cat-proc))) - (add-msg :system (format nil "* cat pid=~a *" (uiop:process-info-pid cat-proc))) + :stderr nil) + *tty-in* (uiop:process-info-output *cat-proc*))) + (add-msg :system (format nil "* cat pid=~a *" (uiop:process-info-pid *cat-proc*))) ;; Guard against nil w/h from backend-size (setq w (or (and (numberp w) (> w 0) w) 80) h (or (and (numberp h) (> h 0) h) 24)) @@ -990,7 +995,7 @@ Event handlers + daemon I/O + main loop. ;; Keyboard reader: block on cat pipe with 0.1s timeout. (handler-case (sb-ext:with-timeout 0.1 - (let ((raw-ch (read-char tty-in nil nil))) + (let ((raw-ch (read-char *tty-in* nil nil))) (when raw-ch (let ((code (char-code raw-ch))) (queue-event @@ -1064,12 +1069,12 @@ Event handlers + daemon I/O + main loop. (cond (cat (theme-color :dim)) (sel-p (theme-color :accent)) (t (theme-color :agent-fg))) - nil :bold sel-p) - (incf y-off))))))) + nil :bold sel-p) + (incf y-off))))))) (sleep 0.1)) - (uiop:terminate-process cat-proc)) - (add-msg :system (format nil "* cat ~a ended *" (uiop:process-info-pid cat-proc)))) - (progn (disconnect-daemon)))) + (uiop:terminate-process *cat-proc*)) + (add-msg :system (format nil "* cat ~a ended *" (uiop:process-info-pid *cat-proc*)))) + (progn (disconnect-daemon))) #+END_SRC * Test Suite