Fix vterm: alt-screen gaps, line wrap, and column width calculation

ober

46bddacdd98e519ef5b00323947b27e3d84b9b13

diff --git a/lib/jerboa-emacs/qt/app.sls b/lib/jerboa-emacs/qt/app.sls
index dd6a38f..57b2d88 100644
--- a/lib/jerboa-emacs/qt/app.sls
+++ b/lib/jerboa-emacs/qt/app.sls
@@ -173,10 +173,12 @@
                  (vtscreen-clear-damage! vt)
                  (hash-put! *vterm-dirty-rows* ts (make-hash-table))
                  #f]
-                [(and (> n 4) (not (vtscreen-alt-screen? vt)))
+                [(or (vtscreen-alt-screen? vt) (> n 4))
                  (let* ([rendered (vtscreen-render vt)]
-                        [pre-text (or (terminal-state-pre-pty-text ts)
-                                      "")])
+                        [pre-text (if (vtscreen-alt-screen? vt)
+                                      ""
+                                      (or (terminal-state-pre-pty-text ts)
+                                          ""))])
                    (let update ([r2 0])
                      (when (< r2 rows)
                        (vector-set! cache r2 (vtscreen-get-row-text vt r2))
@@ -1788,10 +1790,16 @@
                                                            2370
                                                            0))]
                                           [widget-w (qt-widget-width ed)]
+                                          [char-w (max 1
+                                                       (sci-send/string
+                                                         ed
+                                                         2275
+                                                         "0"
+                                                         0))]
                                           [new-cols (max 20
                                                          (quotient
                                                            widget-w
-                                                           8))]
+                                                           char-w))]
                                           [old-rows (vtscreen-rows vt)]
                                           [old-cols (vtscreen-cols vt)])
                                      (when (or (not (= new-rows old-rows))
diff --git a/lib/jerboa-emacs/qt/buffer.sls b/lib/jerboa-emacs/qt/buffer.sls
index 2ea7868..6a48307 100644
--- a/lib/jerboa-emacs/qt/buffer.sls
+++ b/lib/jerboa-emacs/qt/buffer.sls
@@ -49,6 +49,10 @@
          (doc-editor-register! doc editor)
          (let ([ro (sci-send editor SCI_GETREADONLY)])
            (sci-send editor SCI_SETREADONLY ro))
+         (let ([lang (buffer-lexer-lang buf)])
+           (qt-plain-text-edit-set-line-wrap!
+             editor
+             (not (or (eq? lang 'terminal) (eq? lang 'shell)))))
          (verbose-log!
            "qt-buffer-attach! post-buffer-attach-hook begin")
          (run-hooks! 'post-buffer-attach-hook editor buf)
diff --git a/lib/jerboa-emacs/qt/commands-config.sls b/lib/jerboa-emacs/qt/commands-config.sls
index 00ae763..b9ac435 100644
--- a/lib/jerboa-emacs/qt/commands-config.sls
+++ b/lib/jerboa-emacs/qt/commands-config.sls
@@ -871,6 +871,7 @@
                             "*")))]
               [buf (qt-buffer-create! name ed #f)])
          (buffer-lexer-lang-set! buf 'terminal)
+         (qt-plain-text-edit-set-line-wrap! ed #f)
          (verbose-log! "cmd-term: qt-buffer-attach! begin")
          (qt-buffer-attach! ed buf)
          (verbose-log! "cmd-term: qt-buffer-attach! done")
diff --git a/src/jerboa-emacs/qt/app.ss b/src/jerboa-emacs/qt/app.ss
index 536887f..56b693f 100644
--- a/src/jerboa-emacs/qt/app.ss
+++ b/src/jerboa-emacs/qt/app.ss
@@ -201,12 +201,14 @@
             (vtscreen-clear-damage! vt)
             (hash-put! *vterm-dirty-rows* ts (make-hash-table))
             #f)
-           ((and (> n 4) (not (vtscreen-alt-screen? vt)))
-            ;; Many dirty rows in normal mode — full set-text! is faster than N line replacements.
-            ;; Never do this on alt screen (top, htop, etc.) — set-text! replaces the entire
-            ;; document, causing QScintilla to reset scroll/layout and visibly bounce.
+           ;; Alt-screen (top, htop, vim) OR many dirty rows:
+           ;; Full set-text! is more reliable than per-line replacement.
+           ;; setUpdatesEnabled batching (in the caller) prevents bounce.
+           ((or (vtscreen-alt-screen? vt) (> n 4))
             (let* ((rendered (vtscreen-render vt))
-                   (pre-text (or (terminal-state-pre-pty-text ts) "")))
+                   (pre-text (if (vtscreen-alt-screen? vt)
+                               ""  ;; alt-screen: no pre-PTY prefix
+                               (or (terminal-state-pre-pty-text ts) ""))))
               ;; Update cache for all rows
               (let update ((r2 0))
                 (when (< r2 rows)
@@ -218,7 +220,7 @@
               (qt-plain-text-edit-set-text! ed (string-append pre-text rendered))
               #t))
            (else
-            ;; Per-line replacement — only update rows whose text actually changed
+            ;; Few dirty rows in normal mode — per-line replacement
             (vterm-ensure-lines! ed (+ line-offset rows))
             (let ((dirty-set (make-hash-table)))
               (let loop ((r2 0) (any-changed? #f))
@@ -1385,7 +1387,10 @@
                               (let* ((ed (qt-edit-window-editor (car wins)))
                                      (new-rows (max 2 (sci-send ed 2370 0)))
                                      (widget-w (qt-widget-width ed))
-                                     (new-cols (max 20 (quotient widget-w 8)))
+                                     ;; 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)))
                                      (old-rows (vtscreen-rows vt))
                                      (old-cols (vtscreen-cols vt)))
                                 (when (or (not (= new-rows old-rows))
diff --git a/src/jerboa-emacs/qt/buffer.ss b/src/jerboa-emacs/qt/buffer.ss
index f866c9d..9e4708e 100644
--- a/src/jerboa-emacs/qt/buffer.ss
+++ b/src/jerboa-emacs/qt/buffer.ss
@@ -68,6 +68,12 @@
     ;; subsequent buffers uneditable.
     (let ((ro (sci-send editor SCI_GETREADONLY)))
       (sci-send editor SCI_SETREADONLY ro))
+    ;; Terminal buffers: disable line wrap (each vtscreen row = one visual line).
+    ;; Non-terminal buffers: enable word wrap for readability.
+    ;; Must be set per buffer-switch since wrap is a widget property, not per-document.
+    (let ((lang (buffer-lexer-lang buf)))
+      (qt-plain-text-edit-set-line-wrap! editor
+        (not (or (eq? lang 'terminal) (eq? lang 'shell)))))
     (verbose-log! "qt-buffer-attach! post-buffer-attach-hook begin")
     ;; Toggle image/editor display via hook (set up in qt/app.ss)
     (run-hooks! 'post-buffer-attach-hook editor buf)
diff --git a/src/jerboa-emacs/qt/commands-config.ss b/src/jerboa-emacs/qt/commands-config.ss
index c87c2c4..22baf66 100644
--- a/src/jerboa-emacs/qt/commands-config.ss
+++ b/src/jerboa-emacs/qt/commands-config.ss
@@ -715,6 +715,8 @@ modified so the next save uses the new encoding."
          (buf (qt-buffer-create! name ed #f)))
     ;; Mark as terminal buffer
     (set! (buffer-lexer-lang buf) 'terminal)
+    ;; Terminal: disable line wrap — each vtscreen row must be one visual line
+    (qt-plain-text-edit-set-line-wrap! ed #f)
     ;; Attach buffer to editor
     (verbose-log! "cmd-term: qt-buffer-attach! begin")
     (qt-buffer-attach! ed buf)