v0.8.0: fix cursor off-by-one in word-wrap — use < instead of <=, incf accum len instead of 1+len

The position-cursor function now uses cursor-line/cursor-col stored by
view-input instead of recomputing from scratch, guaranteeing alignment
with the rendered text. The boundary check uses (< pos (+ accum len))
to avoid falsely matching the first character of the next wrapped line.

Removed the speculative reset-recovery code that sent cursor to end
when pos was 0, since that broke legitimate navigation to the
beginning of the input.
This commit is contained in:
2026-05-18 15:06:13 -04:00
parent 1427e662e2
commit 2f1abee930

View File

@@ -218,7 +218,7 @@ and current sidebar mode (:auto/:visible/:hidden)."
;; Speaker lines for all input rows ;; Speaker lines for all input rows
(dotimes (r panel-rows) (dotimes (r panel-rows)
(cl-tty.backend:draw-text fb hpad (+ panel-top r) "│" (theme-color :input-prompt) nil)) (cl-tty.backend:draw-text fb hpad (+ panel-top r) "│" (theme-color :input-prompt) nil))
;; Draw each wrapped input line ;; Draw each wrapped input line, tracking display position of cursor
(let ((accum 0) (cl 0) (cc 0)) (let ((accum 0) (cl 0) (cc 0))
(dotimes (i n-lines) (dotimes (i n-lines)
(let* ((line (nth i lines)) (let* ((line (nth i lines))
@@ -226,9 +226,9 @@ and current sidebar mode (:auto/:visible/:hidden)."
(len (length line))) (len (length line)))
(when (>= row (- h 4)) (return)) (when (>= row (- h 4)) (return))
(cl-tty.backend:draw-text fb (+ hpad 2) row line input-fg nil) (cl-tty.backend:draw-text fb (+ hpad 2) row line input-fg nil)
(when (and (>= pos accum) (<= pos (+ accum len))) (when (and (>= pos accum) (or (< pos (+ accum len)) (= i (1- n-lines))))
(setf cl i cc (- pos accum))) (setf cl i cc (- pos accum)))
(incf accum (1+ len)))) (incf accum len)))
(setf (st :cursor-line) cl (st :cursor-col) cc)) (setf (st :cursor-line) cl (st :cursor-col) cc))
;; Hint bar at h-2: F:/MCP: on left, token gauge + keybindings on right ;; Hint bar at h-2: F:/MCP: on left, token gauge + keybindings on right
(let* ((focal (or (st :foveal-id) "-")) (let* ((focal (or (st :foveal-id) "-"))
@@ -343,35 +343,26 @@ and current sidebar mode (:auto/:visible/:hidden)."
(setf (st :dirty) (list nil nil nil)))) (setf (st :dirty) (list nil nil nil))))
(defun position-cursor (fb w h) (defun position-cursor (fb w h)
"Draw cursor at the input insertion point using reverse video (Emacs-style)." "Draw cursor at the input insertion point using reverse video (Emacs-style).
(let* ((sw (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0)) Uses cursor-line/cursor-col stored by view-input to stay aligned with rendering."
(cw (- w sw)) (let* ((text (input-string))
(hpad 2)
(text (input-string))
(text-len (length text)) (text-len (length text))
(pos (or (st :cursor-pos) 0)) (pos (or (st :cursor-pos) 0))
(prompt-w (- cw (* 2 hpad) 2)) (cl (or (st :cursor-line) 0))
(cc (or (st :cursor-col) 0))
(hpad 2)
(sw (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0))
(cw (- w sw))
(inner-w (- cw (* 2 hpad)))
(prompt-w (- inner-w 2))
(lines (cl-tty.box:word-wrap text prompt-w)) (lines (cl-tty.box:word-wrap text prompt-w))
(n-lines (max 1 (length lines))) (n-lines (max 1 (length lines)))
(cl 0) (cc 0) (accum 0)) (panel-rows (max 4 (+ n-lines 2)))
;; Find which wrapped line the cursor falls on
(dotimes (i n-lines)
(let ((len (length (nth i lines))))
(when (and (>= pos accum) (or (<= pos (+ accum len))
(= i (1- n-lines))))
(setf cl i cc (- pos accum)))
(incf accum (1+ len))))
;; If text exists but pos is 0, move cursor to end (recovery for pos reset)
(when (and (plusp text-len) (zerop pos))
(setf pos text-len
cl (1- n-lines)
cc (length (car (last lines)))))
(let* ((panel-rows (max 4 (+ n-lines 2)))
(panel-top (- h 4 panel-rows -1)) (panel-top (- h 4 panel-rows -1))
(cx (+ hpad 2 cc))
(cy (+ panel-top 1 cl))
(bg-i (theme-color :bg-input)) (bg-i (theme-color :bg-input))
(input-fg (theme-color :input-fg))) (input-fg (theme-color :input-fg)))
(let ((cx (+ hpad 2 cc))
(cy (+ panel-top 1 cl)))
(if (< pos text-len) (if (< pos text-len)
(let ((ch (char text pos))) (let ((ch (char text pos)))
(cl-tty.backend:draw-text fb cx cy (string ch) bg-i input-fg)) (cl-tty.backend:draw-text fb cx cy (string ch) bg-i input-fg))