fix: replace cl-tty.input:read-event with direct read-raw-byte + inline CSI detection

cl-tty.input:read-event has bugs (CSI parser timeout causes :escape
to be returned for arrow keys). Replace with direct read-raw-byte
calls that are proven to work for CSI sequences. The inline detection:
- Read first byte with 100ms timeout
- If ESC (27), read two more bytes with 150ms timeout each
- Map 65→:up, 66→:down, 67→:right, 68→:left, etc.
- Other bytes converted via the same cond chain as before
Also re-add resize check (was handled by read-event).

Use handler-case around the reader to prevent any reader errors
from crashing the TUI. Re-add Swank *error-output* redirect.
This commit is contained in:
2026-05-15 15:23:12 -04:00
parent bd72175d5b
commit 8d9520a9cb

View File

@@ -963,35 +963,47 @@ Returns T on success, nil on failure. Does NOT wait or retry."
(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 via cl-tty.input:read-event (handles CSI/UTF-8/resize) ;; Keyboard reader via read-raw-byte (proven CSI detection)
(let ((ev (cl-tty.input:read-event be :timeout 0.1))) (handler-case
(when ev (let* ((b (cl-tty.input::read-raw-byte :timeout 0.1))
(if (eq ev :resize) (code b)
(progn (esc-seq (and (= code 27)
(multiple-value-setq (w h) (cl-tty.backend:backend-size be)) (let ((b2 (cl-tty.input::read-raw-byte :timeout 0.15)))
(setq w (or (and (numberp w) (> w 0) w) 80) (when (= b2 91)
h (or (and (numberp h) (> h 0) h) 24)) (let ((t2 (cl-tty.input::read-raw-byte :timeout 0.15)))
(setf (st :dirty) (list t t t))) (and t2 (case t2
(let* ((key (cl-tty.input:key-event-key ev)) (65 :up) (66 :down)
(code (cl-tty.input:key-event-code ev)) (67 :right) (68 :left)
(ctrl (cl-tty.input:key-event-ctrl ev)) (72 :home) (70 :end)
(ch (cond (otherwise :escape)))))))))
((member key (when b
'(:up :down :right :left :escape :enter :tab (queue-event
:backspace :home :end :delete (list :type :key
:page-up :page-down :insert)) :payload (list :code (or code 0)
key) :ch (or esc-seq
(ctrl (cond
(ignore-errors ((= code 13) :enter)
(intern (format nil "CTRL-~a" key) :keyword))) ((= code 10) :enter)
((or (eq key :codepoint) code) ((= code 27) :escape)
(or code 0)) ((= code 9) :tab)
(t nil)))) ((or (= code 127) (= code 8)) :backspace)
((and (>= code 1) (<= code 26))
(when ch (intern
(queue-event (string-upcase
(list :type :key (format nil "CTRL-~a"
:payload (list :code (or code 0) :ch ch)))))))) (code-char (+ #x60 code))))
:keyword))
(t code))))))))
(error (c)
(add-msg :system (format nil "* Reader error: ~a *" c))))
;; Check for terminal resize (SIGWINCH sets this flag)
(when (boundp 'cl-tty.input::*terminal-resized-p*)
(when cl-tty.input::*terminal-resized-p*
(setf cl-tty.input::*terminal-resized-p* nil)
(multiple-value-setq (w h) (cl-tty.backend:backend-size be))
(setq w (or (and (numberp w) (> w 0) w) 80)
h (or (and (numberp h) (> h 0) h) 24))
(setf (st :dirty) (list t t t))))
;; Guard w and h before render (resize or other code may have set them to nil) ;; Guard w and h before render (resize or other code may have set them to nil)
(setq w (or (and (numberp w) (> w 0) w) 80) (setq w (or (and (numberp w) (> w 0) w) 80)
h (or (and (numberp h) (> h 0) h) 24)) h (or (and (numberp h) (> h 0) h) 24))