TUI: fix mid-word truncation of assistant text via per-block wrap width
ober
8a80d79b05cde17a4ac296dbea976689f29231b6
--- a/src/jcode/ui/tui-message.ss +++ b/src/jcode/ui/tui-message.ss @@ -10,6 +10,7 @@ msg-block-content-set! msg-block-tool-status-set! msg-block-collapsed?-set! msg-block-thinking-set! msg-block-thinking-collapsed?-set! + msg-block-wrap-width msg-block-wrap-width-set! reflow-message! render-msg-block! msg-block-user msg-block-assistant msg-block-expert msg-block-tool msg-block-error msg-block-system) @@ -34,18 +35,19 @@ collapsed? ;; #t = show summary only (tools) metadata ;; alist thinking ;; string or #f — extracted <think> content (assistant only) - thinking-collapsed?) ;; #t = show "Reasoning" header only; #f = expand thinking text + thinking-collapsed? ;; #t = show "Reasoning" header only; #f = expand thinking text + wrap-width) ;; cols the `lines` were last wrapped at; -1 = never wrapped transparent: #t) ;; ---- Constructors ---- (def (msg-block-user text) - (let ((m (make-msg-block 'user text '() 0 #f #f #f '() #f #t))) + (let ((m (make-msg-block 'user text '() 0 #f #f #f '() #f #t -1))) (reflow-message! m 80) m)) (def (msg-block-assistant text) - (let ((m (make-msg-block 'assistant text '() 0 #f #f #f '() #f #t))) + (let ((m (make-msg-block 'assistant text '() 0 #f #f #f '() #f #t -1))) (reflow-message! m 80) m)) @@ -53,24 +55,24 @@ ;; block but with a distinct left-border colour so an escalated answer is ;; visually distinguishable from the primary model's output. (def (msg-block-expert text) - (let ((m (make-msg-block 'expert text '() 0 #f #f #f '() #f #t))) + (let ((m (make-msg-block 'expert text '() 0 #f #f #f '() #f #t -1))) (reflow-message! m 80) m)) (def (msg-block-tool name status content . meta) (let ((m (make-msg-block 'tool content '() 0 name status #t (if (null? meta) '() (car meta)) - #f #t))) + #f #t -1))) (reflow-message! m 80) m)) (def (msg-block-error text) - (let ((m (make-msg-block 'error text '() 0 #f #f #f '() #f #t))) + (let ((m (make-msg-block 'error text '() 0 #f #f #f '() #f #t -1))) (reflow-message! m 80) m)) (def (msg-block-system text) - (let ((m (make-msg-block 'system text '() 0 #f #f #f '() #f #t))) + (let ((m (make-msg-block 'system text '() 0 #f #f #f '() #f #t -1))) (reflow-message! m 80) m)) @@ -149,7 +151,8 @@ width))) (let ((wrapped (wrap-segment-lines lines width))) (msg-block-lines-set! msg wrapped) - (msg-block-height-set! msg (length wrapped))))) + (msg-block-height-set! msg (length wrapped)) + (msg-block-wrap-width-set! msg width)))) (def (render-content role content tool-name tool-status collapsed? metadata thinking thinking-collapsed? width) (case role --- a/src/jcode/ui/tui.ss +++ b/src/jcode/ui/tui.ss @@ -1654,6 +1654,13 @@ (skip scroll)) (when (and (pair? msgs) (>= row y)) (let* ((msg (car msgs)) + ;; Invariant: a block's `lines` must be wrapped at the exact + ;; width we render it at, or render-msg-block! hard-clips the + ;; overflow and loses text. The global *last-reflow-width* + ;; guard misses blocks reflowed (mid-stream) at a width that + ;; later changed; this per-block check closes that gap. + (_ (unless (eqv? (msg-block-wrap-width msg) w) + (reflow-message! msg w))) (mh (msg-block-height msg))) (if (> skip 0) (if (>= skip mh)