Fix shell/terminal PTY using hardcoded 80-column width instead of actual buffer width
ober
71934188c5c0e736150dab57afcb088d376cb4eb
--- a/lib/jerboa-emacs/qt/app.sls +++ b/lib/jerboa-emacs/qt/app.sls @@ -1777,6 +1777,68 @@ (when (shell-buffer? buf) (let ([ss (hash-get *shell-state* buf)]) (when (and ss (shell-pty-busy? ss)) + (let ([vt (shell-state-vtscreen ss)]) + (when vt + (let ed-loop ([wins (qt-frame-windows + fr)]) + (when (pair? wins) + (if (eq? (qt-edit-window-buffer + (car wins)) + buf) + (let* ([ed (qt-edit-window-editor + (car wins))] + [new-rows (max 2 + (sci-send + ed + 2370 + 0))] + [widget-w (qt-widget-width + ed)] + [margin-w (sci-send + ed + SCI_GETMARGINWIDTHN + 0)] + [text-w (- widget-w + margin-w + 16)] + [char-w (let ([w (sci-send/string + ed + 2276 + "M" + STYLE_DEFAULT)]) + (if (> w 0) + w + 8))] + [new-cols (max 20 + (quotient + text-w + char-w))] + [old-rows (vtscreen-rows + vt)] + [old-cols (vtscreen-cols + vt)]) + (when (or (not (= new-rows + old-rows)) + (not (= new-cols + old-cols))) + (verbose-log! "SHELL-PTY-RESIZE: " + (number->string old-rows) + "x" + (number->string old-cols) + " -> " + (number->string new-rows) + "x" + (number->string new-cols)) + (vtscreen-resize! + vt + new-rows + new-cols) + (shell-pty-resize! + ss + new-rows + new-cols))) + (ed-loop (cdr wins)))))))) + (when (and ss (shell-pty-busy? ss)) (let drain () (let ([msg (shell-poll-output ss)]) (when msg @@ -1803,15 +1865,24 @@ 0))] [widget-w (qt-widget-width ed)] - [char-w (max 1 - (sci-send/string - ed - 2275 - "0" - 0))] + [margin-w (sci-send + ed + SCI_GETMARGINWIDTHN + 0)] + [text-w (- widget-w + margin-w + 16)] + [char-w (let ([w (sci-send/string + ed + 2276 + "M" + STYLE_DEFAULT)]) + (if (> w 0) + w + 8))] [new-cols (max 20 (quotient - widget-w + text-w char-w))] [old-rows (vtscreen-rows vt)] --- a/lib/jerboa-emacs/qt/commands-edit.sls +++ b/lib/jerboa-emacs/qt/commands-edit.sls @@ -930,67 +930,89 @@ [input (if (> end-pos prompt-pos) (substring all-text prompt-pos end-pos) "")]) - (let ([trimmed-input (safe-string-trim-both input)]) - (when (> (string-length trimmed-input) 0) - (gsh-history-add! trimmed-input (current-directory)))) - (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) - (qt-plain-text-edit-insert-text! ed "\n") - (let-values ([(mode output new-cwd) - (shell-execute-async! input ss)]) - (case mode - [(sync) - (when (and (string? output) - (> (string-length output) 0)) - (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) - (qt-plain-text-edit-insert-text! ed output) - (unless (char=? - (string-ref - output - (- (string-length output) 1)) - #\newline) - (qt-plain-text-edit-insert-text! ed "\n"))) - (when (hash-get *shell-state* buf) - (let ([prompt (shell-prompt ss)]) + (let* ([rows (max 2 (sci-send ed 2370 0))] + [widget-w (qt-widget-width ed)] + [margin-w (sci-send ed SCI_GETMARGINWIDTHN 0)] + [text-w (- widget-w margin-w 16)] + [char-w (let ([w (sci-send/string + ed + 2276 + "M" + STYLE_DEFAULT)]) + (if (> w 0) w 8))] + [cols (max 20 (quotient text-w char-w))]) + (let ([trimmed-input (safe-string-trim-both input)]) + (when (> (string-length trimmed-input) 0) + (gsh-history-add! + trimmed-input + (current-directory)))) + (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) + (qt-plain-text-edit-insert-text! ed "\n") + (let-values ([(mode output new-cwd) + (shell-execute-async! + input + ss + rows + cols)]) + (case mode + [(sync) + (when (and (string? output) + (> (string-length output) 0)) (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) - (qt-plain-text-edit-insert-text! ed prompt) - (shell-state-prompt-pos-set! - ss - (string-length (qt-plain-text-edit-text ed))) - (qt-plain-text-edit-ensure-cursor-visible! ed)))] - [(async) - (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) - (qt-plain-text-edit-ensure-cursor-visible! ed)] - [(special) - (cond - [(eq? output 'clear) - (qt-plain-text-edit-set-text! ed "") - (let ([prompt (shell-prompt ss)]) - (qt-plain-text-edit-insert-text! ed prompt) - (shell-state-prompt-pos-set! - ss - (string-length (qt-plain-text-edit-text ed))) - (qt-plain-text-edit-ensure-cursor-visible! ed))] - [(eq? output 'exit) - (shell-stop! ss) - (let* ([fr (app-state-frame app)] - [other (let loop ([bs (buffer-list)]) - (cond - [(null? bs) #f] - [(eq? (car bs) buf) - (loop (cdr bs))] - [else (car bs)]))]) - (when other - (qt-buffer-attach! ed other) - (qt-edit-window-buffer-set! - (qt-current-window fr) - other)) - (hash-remove! *shell-state* buf) - (qt-buffer-kill! buf) - (echo-message! - (app-state-echo app) - "Shell exited"))])]))))))) + (qt-plain-text-edit-insert-text! ed output) + (unless (char=? + (string-ref + output + (- (string-length output) 1)) + #\newline) + (qt-plain-text-edit-insert-text! ed "\n"))) + (when (hash-get *shell-state* buf) + (let ([prompt (shell-prompt ss)]) + (qt-plain-text-edit-move-cursor! + ed + QT_CURSOR_END) + (qt-plain-text-edit-insert-text! ed prompt) + (shell-state-prompt-pos-set! + ss + (string-length (qt-plain-text-edit-text ed))) + (qt-plain-text-edit-ensure-cursor-visible! + ed)))] + [(async) + (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) + (qt-plain-text-edit-ensure-cursor-visible! ed)] + [(special) + (cond + [(eq? output 'clear) + (qt-plain-text-edit-set-text! ed "") + (let ([prompt (shell-prompt ss)]) + (qt-plain-text-edit-insert-text! ed prompt) + (shell-state-prompt-pos-set! + ss + (string-length + (qt-plain-text-edit-text ed))) + (qt-plain-text-edit-ensure-cursor-visible! + ed))] + [(eq? output 'exit) + (shell-stop! ss) + (let* ([fr (app-state-frame app)] + [other (let loop ([bs (buffer-list)]) + (cond + [(null? bs) #f] + [(eq? (car bs) buf) + (loop (cdr bs))] + [else (car bs)]))]) + (when other + (qt-buffer-attach! ed other) + (qt-edit-window-buffer-set! + (qt-current-window fr) + other)) + (hash-remove! *shell-state* buf) + (qt-buffer-kill! buf) + (echo-message! + (app-state-echo app) + "Shell exited"))])])))))))) (def qt-chat-buffer-name "*AI Chat*") (def qt-chat-prompt "\n\nYou: ") (def (cmd-chat app) --- a/src/jerboa-emacs/qt/app.ss +++ b/src/jerboa-emacs/qt/app.ss @@ -1381,6 +1381,32 @@ (lambda (buf) (when (shell-buffer? buf) (let ((ss (hash-get *shell-state* buf))) + ;; Resize shell PTY + vtscreen when editor dimensions change + (when (and ss (shell-pty-busy? ss)) + (let ((vt (shell-state-vtscreen ss))) + (when vt + (let ed-loop ((wins (qt-frame-windows fr))) + (when (pair? wins) + (if (eq? (qt-edit-window-buffer (car wins)) buf) + (let* ((ed (qt-edit-window-editor (car wins))) + (new-rows (max 2 (sci-send ed 2370 0))) + (widget-w (qt-widget-width ed)) + (margin-w (sci-send ed SCI_GETMARGINWIDTHN 0)) + (text-w (- widget-w margin-w 16)) + (char-w (let ((w (sci-send/string ed 2276 "M" STYLE_DEFAULT))) + (if (> w 0) w 8))) + (new-cols (max 20 (quotient text-w char-w))) + (old-rows (vtscreen-rows vt)) + (old-cols (vtscreen-cols vt))) + (when (or (not (= new-rows old-rows)) + (not (= new-cols old-cols))) + (verbose-log! "SHELL-PTY-RESIZE: " + (number->string old-rows) "x" (number->string old-cols) + " -> " + (number->string new-rows) "x" (number->string new-cols)) + (vtscreen-resize! vt new-rows new-cols) + (shell-pty-resize! ss new-rows new-cols))) + (ed-loop (cdr wins)))))))) (when (and ss (shell-pty-busy? ss)) (let drain () (let ((msg (shell-poll-output ss))) @@ -1403,10 +1429,11 @@ (let* ((ed (qt-edit-window-editor (car wins))) (new-rows (max 2 (sci-send ed 2370 0))) (widget-w (qt-widget-width ed)) - ;; Measure actual character width using Scintilla - ;; SCI_TEXTWIDTH(style, text) = 2275 - (char-w (max 1 (sci-send/string ed 2275 "0" 0))) - (new-cols (max 20 (quotient widget-w char-w))) + (margin-w (sci-send ed SCI_GETMARGINWIDTHN 0)) + (text-w (- widget-w margin-w 16)) + (char-w (let ((w (sci-send/string ed 2276 "M" STYLE_DEFAULT))) + (if (> w 0) w 8))) + (new-cols (max 20 (quotient text-w char-w))) (old-rows (vtscreen-rows vt)) (old-cols (vtscreen-cols vt))) (when (or (not (= new-rows old-rows)) --- a/src/jerboa-emacs/qt/commands-edit.ss +++ b/src/jerboa-emacs/qt/commands-edit.ss @@ -845,6 +845,14 @@ (input (if (> end-pos prompt-pos) (substring all-text prompt-pos end-pos) ""))) + ;; Compute actual terminal dimensions from editor widget + (let* ((rows (max 2 (sci-send ed 2370 0))) ; SCI_LINESONSCREEN + (widget-w (qt-widget-width ed)) + (margin-w (sci-send ed SCI_GETMARGINWIDTHN 0)) + (text-w (- widget-w margin-w 16)) ; 16px for scrollbar + (char-w (let ((w (sci-send/string ed 2276 "M" STYLE_DEFAULT))) + (if (> w 0) w 8))) + (cols (max 20 (quotient text-w char-w)))) ;; Record in shell history (let ((trimmed-input (safe-string-trim-both input))) (when (> (string-length trimmed-input) 0) @@ -852,7 +860,7 @@ ;; Append newline after user input (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) (qt-plain-text-edit-insert-text! ed "\n") - (let-values (((mode output new-cwd) (shell-execute-async! input ss))) + (let-values (((mode output new-cwd) (shell-execute-async! input ss rows cols))) (case mode ((sync) (when (and (string? output) (> (string-length output) 0)) @@ -893,7 +901,7 @@ (set! (qt-edit-window-buffer (qt-current-window fr)) other)) (hash-remove! *shell-state* buf) (qt-buffer-kill! buf) - (echo-message! (app-state-echo app) "Shell exited")))))))))))) + (echo-message! (app-state-echo app) "Shell exited"))))))))))))) ;;;============================================================================ ;;; AI Chat commands (Claude CLI integration) ;;;============================================================================ --- a/src/jerboa-emacs/shell.ss +++ b/src/jerboa-emacs/shell.ss @@ -14,6 +14,7 @@ shell-pty-busy? shell-interrupt! shell-send-input! + shell-pty-resize! shell-cleanup-pty! shell-stop! shell-prompt @@ -266,7 +267,7 @@ (let ((status (shell-pty-waitpid-status pid #t))) (channel-put ch (cons 'done status))))))))))))))) -(def (shell-execute-async! input ss) +(def (shell-execute-async! input ss (pty-rows 24) (pty-cols 80)) "Execute command: builtins go through gsh-capture (sync), external commands go through PTY subprocess (async). Returns: @@ -293,7 +294,7 @@ (values 'sync output cwd)) ;; External/compound: spawn PTY subprocess (async) (let* ((env-alist (env-exported-alist env)) - (rows 24) (cols 80)) + (rows pty-rows) (cols pty-cols)) (let-values (((mfd pid) (with-catch (lambda (e) (jemacs-log! "PTY-SPAWN-ERROR: " @@ -342,6 +343,12 @@ (when mfd (pty-write mfd str)))) +(def (shell-pty-resize! ss rows cols) + "Notify PTY child of window size change." + (let ((mfd (shell-state-pty-master ss))) + (when (and mfd (integer? mfd)) + (pty-resize! mfd rows cols)))) + (def (shell-cleanup-pty! ss) "Clean up PTY state after command finishes or on stop." (let ((mfd (shell-state-pty-master ss))