Subagent review fixes: textarea ignore-w, hit-test recursion, select/tabbar position, X10 release, CSI param < digit, text-input truncation
CRITICAL: Remove (declare (ignore w)) from textarea render (textarea.lisp:251) w is used for horizontal truncation on the next line. Declaring it ignored while using it is undefined behavior in CL (SBCL warns). HIGH: hit-test recurses into children (mouse.lisp:18-34) Was returning the root component for any click within its bounds, ignoring nested widgets entirely. Now checks component-children first, returning the deepest match. MEDIUM: Select/TabBar position hardcoded to (0,0) Both rendered at terminal origin regardless of layout position. Now read layout-node-x/y for absolute positioning. MEDIUM: Text-input truncation missing Render drew full value string even when exceeding widget width. Now truncates to (min (length display) w). MEDIUM: X10 mouse release detection added (input.lisp:219-226) X10 encoding uses button=3 for release. Was detecting all events as press/drag. Now checks button=3 → :release. MEDIUM: parse-csi-params handles private markers (input.lisp:128-131) < = > ? characters (0x3c-0x3f) treated as parameter start markers instead of accumulating bogus digit values. Latent trap removed. Deferred (pre-existing design): - Scrollbox visibility cy vs orig-y: match for column layout (common case) - Nested scrollbox coordinates: assumes sequential layout positions - text-input cursor drawing: feature, not bugfix 392 tests pass.
This commit is contained in:
@@ -124,7 +124,10 @@
|
||||
((and (>= b #x30) (<= b #x3f))
|
||||
(if (char= (code-char b) #\;)
|
||||
(progn (push current params) (setf current 0))
|
||||
(setf current (+ (* current 10) (- b #x30)))))
|
||||
;; Non-digit parameter characters (< = > ?) start a new param at zero
|
||||
(if (member b '(#x3c #x3d #x3e #x3f) :test #'=)
|
||||
(setf current 0)
|
||||
(setf current (+ (* current 10) (- b #x30))))))
|
||||
((and (>= b #x20) (<= b #x2f))
|
||||
nil)
|
||||
((and (>= b #x40) (<= b #x7e))
|
||||
@@ -219,15 +222,12 @@
|
||||
(y (third params))
|
||||
(button (logand p0 #x03))
|
||||
(motion (logand p0 #x20))
|
||||
(wheel (logand p0 #x40)))
|
||||
(release (= button 3)))
|
||||
(make-mouse-event
|
||||
:type (if motion :drag :press)
|
||||
:button (cond (wheel (if (zerop (logand p0 #x01))
|
||||
:wheel-up :wheel-down))
|
||||
((= button 0) :left)
|
||||
((= button 1) :middle)
|
||||
((= button 2) :right)
|
||||
(t :none))
|
||||
:type (cond (release :release)
|
||||
(motion :drag)
|
||||
(t :press))
|
||||
:button (let ((b button)) (cond ((= b 0) :left) ((= b 1) :middle) ((= b 2) :right) (t :none)))
|
||||
:x x :y y :raw (format nil "~C[<~d;~d;~d~C" #\Esc p0 x y (code-char final-byte))))
|
||||
(let* ((tilde-p (char= (code-char final-byte) #\~))
|
||||
(param (or p0 0))
|
||||
|
||||
@@ -17,17 +17,26 @@
|
||||
|
||||
(defun hit-test (root x y)
|
||||
"Find the deepest component at (X, Y) by testing layout-node bounds.
|
||||
Recurses into component-children to find the innermost match.
|
||||
Components without a layout-node or position return nil."
|
||||
(labels ((recurse (node)
|
||||
(let ((ln (ignore-errors (component-layout-node node))))
|
||||
(let ((ln (ignore-errors (component-layout-node node)))
|
||||
(best nil))
|
||||
(when ln
|
||||
(let ((nx (layout-node-x ln))
|
||||
(ny (layout-node-y ln))
|
||||
(nw (layout-node-width ln))
|
||||
(nh (layout-node-height ln)))
|
||||
;; Check children first for deeper match
|
||||
(dolist (child (ignore-errors (component-children node)))
|
||||
(let ((child-hit (recurse child)))
|
||||
(when child-hit
|
||||
(setf best child-hit))))
|
||||
;; If no child matched, check self
|
||||
(or best
|
||||
(when (and (>= x nx) (< x (+ nx nw))
|
||||
(>= y ny) (< y (+ ny nh)))
|
||||
node))))))
|
||||
node)))))))
|
||||
(recurse root)))
|
||||
|
||||
;; Selection
|
||||
|
||||
@@ -76,7 +76,9 @@
|
||||
(subseq filtered start end)))
|
||||
|
||||
(defmethod render ((sel select) backend)
|
||||
(let* ((ln (select-layout-node sel)) (x 0) (y 0)
|
||||
(let* ((ln (select-layout-node sel))
|
||||
(x (if ln (layout-node-x ln) 0))
|
||||
(y (if ln (layout-node-y ln) 0))
|
||||
(w (if ln (layout-node-width ln) 80))
|
||||
(visible (select-visible-options sel)) (sel-idx (select-selected-index sel)))
|
||||
(dolist (item visible)
|
||||
|
||||
@@ -35,9 +35,11 @@
|
||||
(case (key-event-key event) (:left (tab-bar-prev tb) t) (:right (tab-bar-next tb) t) (t nil)))
|
||||
|
||||
(defmethod render ((tb tab-bar) backend)
|
||||
(let* ((ln (tab-bar-layout-node tb)) (y 0)
|
||||
(let* ((ln (tab-bar-layout-node tb))
|
||||
(x (if ln (layout-node-x ln) 0))
|
||||
(y (if ln (layout-node-y ln) 0))
|
||||
(w (if ln (layout-node-width ln) 80))
|
||||
(active-id (tab-bar-active tb)) (tabs (tab-bar-tabs tb)) (x-pos 0))
|
||||
(active-id (tab-bar-active tb)) (tabs (tab-bar-tabs tb)) (x-pos x))
|
||||
(dolist (tab tabs)
|
||||
(let* ((id (getf tab :id)) (title (getf tab :title))
|
||||
(label (format nil " ~A " title)) (label-len (length label))
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
(cursor (text-input-cursor in))
|
||||
(display (if (plusp (length value))
|
||||
value
|
||||
(or (text-input-placeholder in) ""))))
|
||||
(or (text-input-placeholder in) "")))
|
||||
(truncated (subseq display 0 (min (length display) w))))
|
||||
(declare (ignore w cursor))
|
||||
(draw-text backend x y display nil nil)))
|
||||
(draw-text backend x y truncated nil nil)))
|
||||
|
||||
BIN
src/components/textarea.fasl
Normal file
BIN
src/components/textarea.fasl
Normal file
Binary file not shown.
@@ -248,7 +248,6 @@
|
||||
(h (if ln (layout-node-height ln) 24))
|
||||
(lines (textarea-lines ta))
|
||||
(max-lines (min (length lines) h)))
|
||||
(declare (ignore w))
|
||||
(loop for i from 0 below max-lines
|
||||
for line in lines
|
||||
do (draw-text backend x (+ y i)
|
||||
|
||||
Reference in New Issue
Block a user