fix: all CSI parser reads need timeout, select-next skips all categorized items
- %read-escape-sequence: increase b1 timeout 0.05→0.1, pass timeout to parse-csi-sequence and all read-next calls (OCR branch was using nil timeout, blocking forever) - parse-csi-sequence: accept :timeout keyword, pass to all read-raw-byte calls, return :escape on timeout instead of blocking - %read-digits: accept timeout, check nil from read-raw-byte before (>= b 48) - %parse-sgr-mouse: accept timeout, return nil if first byte times out - read-param in parse-csi-sequence: check b for nil before comparing - parse-csi-params: map Kitty protocol u-terminator cursor codes (1=up, 2=down, 3=right, 4=left, 5=page-up, 6=page-down) before falling to :codepoint. Convert terminator byte to char via code-char for key table lookups. - select-next/select-prev: remove (not (getf opt :category)) check. All items have :category in the unified command list, so navigation skipped every item and selection stayed at index 0 permanently.
This commit is contained in:
@@ -44,8 +44,18 @@
|
|||||||
|
|
||||||
(defun parse-csi-params (params terminator extended)
|
(defun parse-csi-params (params terminator extended)
|
||||||
(let* ((term-char (code-char terminator))
|
(let* ((term-char (code-char terminator))
|
||||||
(key (if (find term-char '(#\~ #\u))
|
(tilde-key (when (find term-char '(#\~ #\u))
|
||||||
(cdr (assoc (first params) *csi-tilde-table*))
|
(cdr (assoc (first params) *csi-tilde-table*))))
|
||||||
|
;; If tilde/u lookup fails, try direct csi-key-table match (ESC[A etc.)
|
||||||
|
;; For u-terminator with cursor codes (1=up,2=down,3=right,4=left) that
|
||||||
|
;; aren't in *csi-tilde-table* (which maps 1→:home,2→:insert,3→:delete...),
|
||||||
|
;; handle them explicitly before falling through to :codepoint.
|
||||||
|
(key (or tilde-key
|
||||||
|
(and (eql term-char #\u)
|
||||||
|
(case (first params)
|
||||||
|
(1 :up) (2 :down) (3 :right) (4 :left)
|
||||||
|
(5 :page-up) (6 :page-down)
|
||||||
|
(otherwise (cdr (assoc (first params) *csi-tilde-table*)))))
|
||||||
(cdr (assoc term-char *csi-key-table*))))
|
(cdr (assoc term-char *csi-key-table*))))
|
||||||
(modifier (when (and (> (length params) 1) (not (find term-char '(#\~ #\u))))
|
(modifier (when (and (> (length params) 1) (not (find term-char '(#\~ #\u))))
|
||||||
(second params)))
|
(second params)))
|
||||||
@@ -59,7 +69,8 @@
|
|||||||
(setf shift (or shift (logtest actual-modifier 1))
|
(setf shift (or shift (logtest actual-modifier 1))
|
||||||
alt (or alt (logtest actual-modifier 2))
|
alt (or alt (logtest actual-modifier 2))
|
||||||
ctrl (or ctrl (logtest actual-modifier 4))))
|
ctrl (or ctrl (logtest actual-modifier 4))))
|
||||||
(if (eql term-char #\u)
|
(if (and (eql term-char #\u) (not key))
|
||||||
|
;; Kitty protocol with unknown codepoint — send as :codepoint
|
||||||
(let ((code (first params)))
|
(let ((code (first params)))
|
||||||
(make-key-event :key :codepoint :code code
|
(make-key-event :key :codepoint :code code
|
||||||
:ctrl ctrl :alt alt :shift shift
|
:ctrl ctrl :alt alt :shift shift
|
||||||
@@ -70,6 +81,7 @@
|
|||||||
(format nil "~C[~{~d~};~d" #\Esc params terminator)
|
(format nil "~C[~{~d~};~d" #\Esc params terminator)
|
||||||
"")))))
|
"")))))
|
||||||
|
|
||||||
|
|
||||||
(defun read-raw-byte (&key timeout)
|
(defun read-raw-byte (&key timeout)
|
||||||
(let* ((buf (make-array 1 :element-type '(unsigned-byte 8)))
|
(let* ((buf (make-array 1 :element-type '(unsigned-byte 8)))
|
||||||
(fd 0)
|
(fd 0)
|
||||||
@@ -85,16 +97,16 @@
|
|||||||
(let ((n (sb-unix:unix-read fd sap 1)))
|
(let ((n (sb-unix:unix-read fd sap 1)))
|
||||||
(if (= n 1) (aref buf 0) (values nil :eof))))))))
|
(if (= n 1) (aref buf 0) (values nil :eof))))))))
|
||||||
|
|
||||||
(defun %read-escape-sequence ()
|
(defun %read-escape-sequence (&optional (timeout 0.1))
|
||||||
(flet ((read-next (&optional (timeout nil))
|
(flet ((read-next (&optional (to timeout))
|
||||||
(let ((b (read-raw-byte :timeout timeout)))
|
(let ((b (read-raw-byte :timeout to)))
|
||||||
(unless b (return-from %read-escape-sequence
|
(unless b (return-from %read-escape-sequence
|
||||||
(make-key-event :key :escape :code 27)))
|
(make-key-event :key :escape :code 27)))
|
||||||
b)))
|
b)))
|
||||||
(let ((b1 (read-next 0.05)))
|
(let ((b1 (read-next timeout)))
|
||||||
(cond
|
(cond
|
||||||
((null b1) (make-key-event :key :escape :code 27))
|
((null b1) (make-key-event :key :escape :code 27))
|
||||||
((= b1 79) (let ((b2 (read-next)))
|
((= b1 79) (let ((b2 (read-next timeout)))
|
||||||
(case b2
|
(case b2
|
||||||
(80 (make-key-event :key :f1))
|
(80 (make-key-event :key :f1))
|
||||||
(81 (make-key-event :key :f2))
|
(81 (make-key-event :key :f2))
|
||||||
@@ -107,7 +119,7 @@
|
|||||||
(67 (make-key-event :key :right :shift t))
|
(67 (make-key-event :key :right :shift t))
|
||||||
(68 (make-key-event :key :left :shift t))
|
(68 (make-key-event :key :left :shift t))
|
||||||
(otherwise (make-key-event :key :unknown :raw (string (code-char b2)))))))
|
(otherwise (make-key-event :key :unknown :raw (string (code-char b2)))))))
|
||||||
((= b1 91) (parse-csi-sequence))
|
((= b1 91) (parse-csi-sequence :timeout timeout))
|
||||||
((= b1 127) (make-key-event :key :alt-backspace))
|
((= b1 127) (make-key-event :key :alt-backspace))
|
||||||
((< b1 32)
|
((< b1 32)
|
||||||
(let ((c (code-char (+ b1 96))))
|
(let ((c (code-char (+ b1 96))))
|
||||||
@@ -116,15 +128,15 @@
|
|||||||
(t (make-key-event :key (intern (string-upcase (string (code-char b1))) :keyword)
|
(t (make-key-event :key (intern (string-upcase (string (code-char b1))) :keyword)
|
||||||
:alt t :code b1))))))
|
:alt t :code b1))))))
|
||||||
|
|
||||||
(defun %read-digits (&optional (initial-bytes nil))
|
(defun %read-digits (&optional (initial-bytes nil) (timeout 0.5))
|
||||||
"Read bytes until a non-digit is encountered.
|
"Read bytes until a non-digit is encountered.
|
||||||
Returns (values number terminator-byte)."
|
Returns (values number terminator-byte)."
|
||||||
(let ((acc nil))
|
(let ((acc nil))
|
||||||
(dolist (b initial-bytes)
|
(dolist (b initial-bytes)
|
||||||
(when (and (>= b 48) (<= b 57))
|
(when (and (>= b 48) (<= b 57))
|
||||||
(push (- b 48) acc)))
|
(push (- b 48) acc)))
|
||||||
(loop for b = (read-raw-byte)
|
(loop for b = (read-raw-byte :timeout timeout)
|
||||||
while (and (>= b 48) (<= b 57))
|
while (and b (>= b 48) (<= b 57))
|
||||||
do (push (- b 48) acc)
|
do (push (- b 48) acc)
|
||||||
finally (return (values (if acc
|
finally (return (values (if acc
|
||||||
(reduce (lambda (n d) (+ (* n 10) d))
|
(reduce (lambda (n d) (+ (* n 10) d))
|
||||||
@@ -132,15 +144,16 @@ Returns (values number terminator-byte)."
|
|||||||
0)
|
0)
|
||||||
b)))))
|
b)))))
|
||||||
|
|
||||||
(defun %parse-sgr-mouse ()
|
(defun %parse-sgr-mouse (&optional (timeout 0.5))
|
||||||
"Parse SGR mouse escape sequence: ESC [ < Cb ; Cx ; Cy M/m
|
"Parse SGR mouse escape sequence: ESC [ < Cb ; Cx ; Cy M/m
|
||||||
Returns a mouse-event struct."
|
Returns a mouse-event struct."
|
||||||
(let ((b (read-raw-byte)))
|
(let ((b (read-raw-byte :timeout timeout)))
|
||||||
(multiple-value-bind (cb sep1) (%read-digits (list b))
|
(unless b (return-from %parse-sgr-mouse nil))
|
||||||
|
(multiple-value-bind (cb sep1) (%read-digits (list b) timeout)
|
||||||
(declare (ignore sep1))
|
(declare (ignore sep1))
|
||||||
(multiple-value-bind (cx sep2) (%read-digits)
|
(multiple-value-bind (cx sep2) (%read-digits nil timeout)
|
||||||
(declare (ignore sep2))
|
(declare (ignore sep2))
|
||||||
(multiple-value-bind (cy term) (%read-digits)
|
(multiple-value-bind (cy term) (%read-digits nil timeout)
|
||||||
(let ((button (cond
|
(let ((button (cond
|
||||||
((= cb 0) :left)
|
((= cb 0) :left)
|
||||||
((= cb 1) :middle)
|
((= cb 1) :middle)
|
||||||
@@ -156,24 +169,35 @@ Returns a mouse-event struct."
|
|||||||
(make-mouse-event :type type :button button
|
(make-mouse-event :type type :button button
|
||||||
:x (- cx 1) :y (- cy 1))))))))
|
:x (- cx 1) :y (- cy 1))))))))
|
||||||
|
|
||||||
(defun parse-csi-sequence ()
|
(defun parse-csi-sequence (&key (timeout 0.1))
|
||||||
(flet ((read-param (next-fn) (let ((acc nil))
|
(flet ((read-param (next-fn) (let ((acc nil))
|
||||||
(loop for b = (funcall next-fn)
|
(loop for b = (funcall next-fn)
|
||||||
do (if (and (>= b 48) (<= b 57))
|
do (if (and b (>= b 48) (<= b 57))
|
||||||
(push (- b 48) acc)
|
(push (- b 48) acc)
|
||||||
(return (values (reverse acc) b)))))))
|
(return (values (reverse acc) b)))))))
|
||||||
(let* ((b2 (read-raw-byte)))
|
(let* ((b2 (read-raw-byte :timeout timeout)))
|
||||||
|
(unless b2 (return-from parse-csi-sequence
|
||||||
|
(make-key-event :key :escape :code 27)))
|
||||||
(if (= b2 60) ;; < — SGR mouse marker
|
(if (= b2 60) ;; < — SGR mouse marker
|
||||||
(%parse-sgr-mouse)
|
(or (%parse-sgr-mouse timeout)
|
||||||
|
(make-key-event :key :escape :code 27))
|
||||||
(let ((extended (make-array 8 :element-type 'fixnum :fill-pointer 0)))
|
(let ((extended (make-array 8 :element-type 'fixnum :fill-pointer 0)))
|
||||||
(multiple-value-bind (params terminator)
|
(multiple-value-bind (params terminator)
|
||||||
(if (and (>= b2 48) (<= b2 57))
|
(if (and (>= b2 48) (<= b2 57))
|
||||||
(multiple-value-bind (p term) (read-param (lambda () (read-raw-byte)))
|
(multiple-value-bind (p term) (read-param (lambda ()
|
||||||
|
(read-raw-byte :timeout timeout)))
|
||||||
|
(unless term (return-from parse-csi-sequence
|
||||||
|
(make-key-event :key :escape :code 27)))
|
||||||
(setf (fill-pointer extended) (length p))
|
(setf (fill-pointer extended) (length p))
|
||||||
(replace extended p)
|
(replace extended p)
|
||||||
(values p term))
|
(values p term))
|
||||||
(progn (vector-push-extend b2 extended)
|
(progn (vector-push-extend b2 extended)
|
||||||
(read-param (lambda () (read-raw-byte)))))
|
(multiple-value-bind (p term)
|
||||||
|
(read-param (lambda ()
|
||||||
|
(read-raw-byte :timeout timeout)))
|
||||||
|
(unless term (return-from parse-csi-sequence
|
||||||
|
(make-key-event :key :escape :code 27)))
|
||||||
|
(values p term))))
|
||||||
(parse-csi-params params terminator extended)))))))
|
(parse-csi-params params terminator extended)))))))
|
||||||
|
|
||||||
(defun utf8-decode (bytes)
|
(defun utf8-decode (bytes)
|
||||||
@@ -194,7 +218,7 @@ Returns a mouse-event struct."
|
|||||||
(multiple-value-bind (b reason) (read-raw-byte :timeout timeout)
|
(multiple-value-bind (b reason) (read-raw-byte :timeout timeout)
|
||||||
(unless b (return-from %read-event (if (eq reason :eof) :eof nil)))
|
(unless b (return-from %read-event (if (eq reason :eof) :eof nil)))
|
||||||
(cond
|
(cond
|
||||||
((= b #x1b) (%read-escape-sequence))
|
((= b #x1b) (%read-escape-sequence timeout))
|
||||||
((= b #x09) (make-key-event :key :tab :code #x09))
|
((= b #x09) (make-key-event :key :tab :code #x09))
|
||||||
((= b #x0a) (make-key-event :key :enter :code #x0a))
|
((= b #x0a) (make-key-event :key :enter :code #x0a))
|
||||||
((= b #x0d) (make-key-event :key :enter :code #x0d))
|
((= b #x0d) (make-key-event :key :enter :code #x0d))
|
||||||
|
|||||||
@@ -58,29 +58,25 @@
|
|||||||
(max 0 (min (select-selected-index sel) (1- count)))))))
|
(max 0 (min (select-selected-index sel) (1- count)))))))
|
||||||
|
|
||||||
(defun select-next (sel)
|
(defun select-next (sel)
|
||||||
"Move selection to next non-category option. Wraps at end."
|
"Move selection to next option. Wraps at end."
|
||||||
(let* ((filtered (select-filtered-options sel))
|
(let* ((filtered (select-filtered-options sel))
|
||||||
(count (length filtered))
|
(count (length filtered))
|
||||||
(current (select-selected-index sel)))
|
(current (select-selected-index sel)))
|
||||||
(when (plusp count)
|
(when (plusp count)
|
||||||
(loop for i from 1 below count
|
(loop for i from 1 below count
|
||||||
for idx = (mod (+ current i) count)
|
for idx = (mod (+ current i) count)
|
||||||
for opt = (third (nth idx filtered))
|
|
||||||
when (not (getf opt :category))
|
|
||||||
do (setf (select-selected-index sel) idx)
|
do (setf (select-selected-index sel) idx)
|
||||||
(mark-dirty sel)
|
(mark-dirty sel)
|
||||||
(return)))))
|
(return)))))
|
||||||
|
|
||||||
(defun select-prev (sel)
|
(defun select-prev (sel)
|
||||||
"Move selection to previous non-category option. Wraps at start."
|
"Move selection to previous option. Wraps at start."
|
||||||
(let* ((filtered (select-filtered-options sel))
|
(let* ((filtered (select-filtered-options sel))
|
||||||
(count (length filtered))
|
(count (length filtered))
|
||||||
(current (select-selected-index sel)))
|
(current (select-selected-index sel)))
|
||||||
(when (plusp count)
|
(when (plusp count)
|
||||||
(loop for i from 1 below count
|
(loop for i from 1 below count
|
||||||
for idx = (mod (- current i) count)
|
for idx = (mod (- current i) count)
|
||||||
for opt = (third (nth idx filtered))
|
|
||||||
when (not (getf opt :category))
|
|
||||||
do (setf (select-selected-index sel) idx)
|
do (setf (select-selected-index sel) idx)
|
||||||
(mark-dirty sel)
|
(mark-dirty sel)
|
||||||
(return)))))
|
(return)))))
|
||||||
|
|||||||
Reference in New Issue
Block a user