fix: runtime crash (sb-ext:timeout undefined), replace with listen-based polling

- Remove handler-case + sb-ext:with-timeout 0.1 pattern entirely.
  sb-ext:timeout is a condition class, not a recognized type in the
  compilation environment, causing runtime 'undefined function' crash.
- Replace with dotimes 10 * (listen *standard-input*) + sleep 0.01
  polling loop. Same 0.1s timeout, no condition type dependencies.
- Also fix handler-bind → handler-case in tui-load.lisp so the stack
  unwinds properly (running with-terminal's shutdown-backend cleanup)
  before the crash handler runs, restoring terminal to normal state.
- Fix paren imbalance (off by 1) in the new listen-based reader code.
This commit is contained in:
2026-05-15 11:36:46 -04:00
parent c1f4ad40d2
commit 9fb4393c9c
2 changed files with 55 additions and 53 deletions

View File

@@ -955,51 +955,51 @@ supplied (e.g. \"/\"), pre-fill the select filter with it."
(when (> (length f) 0) (when (> (length f) 0)
(setf (cl-tty.select:select-filter sel) (setf (cl-tty.select:select-filter sel)
(subseq f 0 (1- f)))))))) (subseq f 0 (1- f))))))))
(on-key ch)))))))) (on-key ch))))))))
;; Keyboard reader with CSI escape detection ;; Keyboard reader: poll with 0.1s timeout via listen (no sb-ext:timeout)
(handler-case (dotimes (_ 10)
(sb-ext:with-timeout 0.1 (when (listen *standard-input*)
(let* ((raw-ch (read-char-no-hang *standard-input* nil nil))) (let* ((raw-ch (read-char-no-hang *standard-input* nil nil))
(when raw-ch (code (and raw-ch (char-code raw-ch)))
(let* ((code (char-code raw-ch)) (esc-seq (and (= code 27)
(esc-seq (and (= code 27) (let ((b nil) (t2 nil))
;; Poll for up to 20ms to collect CSI bytes (dotimes (_ 20)
(let ((b nil) (t2 nil)) (when (listen *standard-input*)
(dotimes (_ 20) (setq b (read-char *standard-input* nil nil))
(when (listen *standard-input*) (return))
(setq b (read-char *standard-input* nil nil)) (sleep 0.001))
(return)) (and b (char= b #\[)
(sleep 0.001)) (dotimes (_ 10)
(and b (char= b #\[) (when (listen *standard-input*)
(dotimes (_ 10) (setq t2 (read-char *standard-input* nil nil))
(when (listen *standard-input*) (return))
(setq t2 (read-char *standard-input* nil nil)) (sleep 0.001))
(return)) (case (and t2 (char-code t2))
(sleep 0.001)) (65 :up) (66 :down)
(case (and t2 (char-code t2)) (67 :right) (68 :left)
(65 :up) (66 :down) (72 :home) (70 :end)
(67 :right) (68 :left) (otherwise :escape)))))))
(72 :home) (70 :end) (when raw-ch
(otherwise :escape)))))) (queue-event
(queue-event (list :type :key
(list :type :key :payload (list :code code
:payload (list :code code :ch (or esc-seq
:ch (or esc-seq (cond
(cond ((= code 13) :enter)
((= code 13) :enter) ((= code 10) :enter)
((= code 10) :enter) ((= code 27) :escape)
((= code 27) :escape) ((= code 9) :tab)
((= code 9) :tab) ((or (= code 127) (= code 8)) :backspace)
((or (= code 127) (= code 8)) :backspace) ((and (>= code 1) (<= code 26))
((and (>= code 1) (<= code 26)) (intern
(intern (string-upcase
(string-upcase (format nil "CTRL-~a"
(format nil "CTRL-~a" (code-char (+ #x60 code))))
(code-char (+ #x60 code)))) :keyword))
:keyword)) (t code)))))))
(t code)))))))))))) (return)))
(sb-ext:timeout ()) (sleep 0.01))
;; Check for terminal resize (SIGWINCH sets this flag) ;; Check for terminal resize (SIGWINCH sets this flag)
(when (boundp 'cl-tty.input::*terminal-resized-p*) (when (boundp 'cl-tty.input::*terminal-resized-p*)
(when cl-tty.input::*terminal-resized-p* (when cl-tty.input::*terminal-resized-p*
(setf cl-tty.input::*terminal-resized-p* nil) (setf cl-tty.input::*terminal-resized-p* nil)

View File

@@ -397,14 +397,16 @@ case "$COMMAND" in
(compile-file src :output-file fasl :verbose nil :print nil)) (compile-file src :output-file fasl :verbose nil :print nil))
(load fasl :verbose nil :print nil)))) (load fasl :verbose nil :print nil))))
(in-package :passepartout) (in-package :passepartout)
(handler-bind ((error (lambda (c) (ignore-errors (handler-case
(with-open-file (f (merge-pathnames ".cache/passepartout/tui-crash.log" (user-homedir-pathname)) (passepartout.channel-tui:tui-main)
:direction :output :if-exists :supersede :if-does-not-exist :create) (error (c)
(format f "CRASH: ~a~%~%" c) (sb-debug:print-backtrace :count 50 :stream f) (finish-output f))) (ignore-errors
(format t "~%=== TUI CRASH ===~%CRASH: ~a~%" c) (with-open-file (f (merge-pathnames ".cache/passepartout/tui-crash.log" (user-homedir-pathname))
(format t "Full backtrace saved to ~~/.cache/passepartout/tui-crash.log~%") :direction :output :if-exists :supersede :if-does-not-exist :create)
(sleep 3) (finish-output) (uiop:quit 1)))) (format f "CRASH: ~a~%~%" c) (sb-debug:print-backtrace :count 50 :stream f) (finish-output f)))
(passepartout.channel-tui:tui-main)) (format t "~%=== TUI CRASH ===~%CRASH: ~a~%" c)
(format t "Full backtrace saved to ~~/.cache/passepartout/tui-crash.log~%")
(sleep 3) (finish-output)))
(uiop:quit 0) (uiop:quit 0)
LISPEOF LISPEOF
# Capture terminal dimensions in non-standard env vars # Capture terminal dimensions in non-standard env vars