fix: CSI escape detection for arrow keys, fix paren balance

- Add CSI escape sequence detection: when ESC (27) is received, poll
  for up to 20ms for the next bytes to detect arrow/home/end keys
- Use listen+read-char polling (not nested with-timeout) to reliably
  collect multi-byte sequences while keeping standalone ESC responsive
- Fix paren balance in main code block (2 extra opens from nested
  esc-seq forms needed matching closes)
This commit is contained in:
2026-05-15 09:43:03 -04:00
parent 53aa471a51
commit 5924994202

View File

@@ -960,30 +960,49 @@ supplied (e.g. \"/\"), pre-fill the select filter with it."
(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))))))))
;; Check for terminal resize (SIGWINCH sets this flag) ;; Keyboard reader with CSI escape detection
;; Keyboard reader: block on cat pipe with 0.1s timeout. (handler-case
(handler-case (sb-ext:with-timeout 0.1
(sb-ext:with-timeout 0.1 (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
(when raw-ch (let* ((code (char-code raw-ch))
(let ((code (char-code raw-ch))) (esc-seq (and (= code 27)
(queue-event ;; Poll for up to 20ms to collect CSI bytes
(list :type :key (let ((b nil) (t2 nil))
:payload (list :code code (dotimes (_ 20)
:ch (cond (when (listen *standard-input*)
((= code 13) :enter) (setq b (read-char *standard-input* nil nil))
((= code 10) :enter) (return))
((= code 27) :escape) (sleep 0.001))
((= code 9) :tab) (and b (char= b #\[)
((or (= code 127) (= code 8)) :backspace) (dotimes (_ 10)
((and (>= code 1) (<= code 26)) (when (listen *standard-input*)
(intern (setq t2 (read-char *standard-input* nil nil))
(string-upcase (return))
(format nil "CTRL-~a" (sleep 0.001))
(code-char (+ #x60 code)))) (case (and t2 (char-code t2))
:keyword)) (65 :up) (66 :down)
(t code))))))))) (67 :right) (68 :left)
(sb-ext:timeout ())) (72 :home) (70 :end)
(otherwise :escape))))))
(queue-event
(list :type :key
:payload (list :code code
:ch (or esc-seq
(cond
((= code 13) :enter)
((= code 10) :enter)
((= code 27) :escape)
((= code 9) :tab)
((or (= code 127) (= code 8)) :backspace)
((and (>= code 1) (<= code 26))
(intern
(string-upcase
(format nil "CTRL-~a"
(code-char (+ #x60 code))))
:keyword))
(t code)))))))))))
(sb-ext:timeout ()))
;; 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*