Add QTerminalWidget with libvterm backend replacing QScintilla terminal

ober

883e6b0b5351b6b90976af59566fa7f41937c0f5

diff --git a/Makefile b/Makefile
index e18305f..e207817 100644
--- a/Makefile
+++ b/Makefile
@@ -46,7 +46,7 @@ libqt_shim.so: vendor/qt_shim.cpp
 	  -I$(QT_SHIM_H) -I$(QT_INC) -I$(QT_INC)/QtCore -I$(QT_INC)/QtGui -I$(QT_INC)/QtWidgets -I$(QT_INC)/Qsci \
 	  vendor/qt_shim.cpp \
 	  -o libqt_shim.so \
-	  -lQt6Core -lQt6Gui -lQt6Widgets -lqscintilla2_qt6
+	  -lQt6Core -lQt6Gui -lQt6Widgets -lqscintilla2_qt6 -lvterm -lutil
 
 qt_chez_shim.so: vendor/qt_chez_shim.c vendor/qt_shim.h
 	gcc -shared -fPIC -O2 -o qt_chez_shim.so vendor/qt_chez_shim.c -Ivendor -Wall
diff --git a/lib/jerboa-emacs/async.sls b/lib/jerboa-emacs/async.sls
index 3cae279..ad44161 100644
--- a/lib/jerboa-emacs/async.sls
+++ b/lib/jerboa-emacs/async.sls
@@ -552,8 +552,8 @@
        (let ([v (hashtable-ref
                   cache
                   key
-                  '#{miss gxqbd0sajiqb4war1wkachpqo-1})])
-         (if (eq? v '#{miss gxqbd0sajiqb4war1wkachpqo-2})
+                  '#{miss l89txjuduef6khmnseoln0n2k-1})])
+         (if (eq? v '#{miss l89txjuduef6khmnseoln0n2k-2})
              (if (null? default) #f (car default))
              v)))
   (def (weak-cache-set! cache key value)
diff --git a/lib/jerboa-emacs/qt/app.sls b/lib/jerboa-emacs/qt/app.sls
index 3264d53..44af117 100644
--- a/lib/jerboa-emacs/qt/app.sls
+++ b/lib/jerboa-emacs/qt/app.sls
@@ -763,24 +763,32 @@
                      (with-output-to-string
                        (lambda () (display-exception e)))))
                  (lambda ()
-                   (if (image-buffer? buf)
-                       (begin
-                         (qt-show-image-buffer! editor buf)
-                         (let ([win (hash-get *editor-window-map* editor)])
-                           (when (and win
-                                      (qt-edit-window-image-scroll win))
-                             (let ([scroll (qt-edit-window-image-scroll
-                                             win)])
-                               (unless (hash-get
-                                         image-key-installed
-                                         scroll)
-                                 ((app-state-key-handler app) scroll)
-                                 (hash-put! image-key-installed scroll #t))
-                               (qt-widget-set-focus! scroll)))))
-                       (begin
-                         (qt-hide-image-buffer! editor)
-                         (qt-reapply-highlighting! editor buf)
-                         (qt-widget-set-focus! editor))))))))
+                   (cond
+                     [(hash-get *terminal-widget-map* buf) =>
+                      (lambda (term)
+                        (let ([win (hash-get *editor-window-map* editor)])
+                          (when win
+                            (let* ([container (qt-edit-window-container
+                                                win)]
+                                   [count (qt-stacked-widget-count
+                                            container)])
+                              (qt-stacked-widget-set-current-index!
+                                container
+                                (- count 1))
+                              (qt-terminal-focus! term)))))]
+                     [(image-buffer? buf)
+                      (qt-show-image-buffer! editor buf)
+                      (let ([win (hash-get *editor-window-map* editor)])
+                        (when (and win (qt-edit-window-image-scroll win))
+                          (let ([scroll (qt-edit-window-image-scroll win)])
+                            (unless (hash-get image-key-installed scroll)
+                              ((app-state-key-handler app) scroll)
+                              (hash-put! image-key-installed scroll #t))
+                            (qt-widget-set-focus! scroll))))]
+                     [else
+                      (qt-hide-image-buffer! editor)
+                      (qt-reapply-highlighting! editor buf)
+                      (qt-widget-set-focus! editor)]))))))
          (recent-files-load!)
          (bookmarks-load! app)
          (custom-keys-load!)
@@ -803,7 +811,7 @@
                             ";;   C-x 2     Split window      C-x o     Other window\n"
                             ";;   C-h f     Describe command   C-h k     Describe key\n"
                             ";;\n"
-                            ";; This buffer is for Gerbil Scheme evaluation.\n"
+                            ";; This buffer is for Jerboa Scheme evaluation.\n"
                             ";; Type expressions and use M-x eval-buffer to evaluate.\n\n"))])
            (qt-plain-text-edit-set-text! ed text)
            (qt-text-document-set-modified!
@@ -1531,203 +1539,259 @@
                                                                                 (app-state-echo
                                                                                   app)
                                                                                 echo-label)))))))])
-                                       (let ([is-printable (and (= (string-length
-                                                                     text)
-                                                                   1)
-                                                                (> (char->integer
-                                                                     (string-ref
-                                                                       text
-                                                                       0))
-                                                                   31))]
-                                             [no-ctrl (zero?
-                                                        (bitwise-and
-                                                          mods
-                                                          QT_MOD_CTRL))]
-                                             [no-alt (zero?
-                                                       (bitwise-and
+                                       (let* ([qt-buf (qt-current-buffer
+                                                        (app-state-frame
+                                                          app))]
+                                              [qt-term (and qt-buf
+                                                            (hash-get
+                                                              *terminal-widget-map*
+                                                              qt-buf))])
+                                         (if (and qt-term
+                                                  (null?
+                                                    (key-state-prefix-keys
+                                                      (app-state-key-state
+                                                        app)))
+                                                  (not (and (not (zero?
+                                                                   (bitwise-and
+                                                                     mods
+                                                                     QT_MOD_CTRL)))
+                                                            (zero?
+                                                              (bitwise-and
+                                                                mods
+                                                                QT_MOD_ALT))
+                                                            (= code
+                                                               (+ QT_KEY_A
+                                                                  23))))
+                                                  (not (and (not (zero?
+                                                                   (bitwise-and
+                                                                     mods
+                                                                     QT_MOD_ALT)))
+                                                            (zero?
+                                                              (bitwise-and
+                                                                mods
+                                                                QT_MOD_CTRL))
+                                                            (= code
+                                                               (+ QT_KEY_A
+                                                                  23)))))
+                                             (qt-terminal-send-key-event!
+                                               qt-term
+                                               code
+                                               mods
+                                               (or text ""))
+                                             (let ([is-printable (and (= (string-length
+                                                                           text)
+                                                                         1)
+                                                                      (> (char->integer
+                                                                           (string-ref
+                                                                             text
+                                                                             0))
+                                                                         31))]
+                                                   [no-ctrl (zero?
+                                                              (bitwise-and
+                                                                mods
+                                                                QT_MOD_CTRL))]
+                                                   [no-alt (zero?
+                                                             (bitwise-and
+                                                               mods
+                                                               QT_MOD_ALT))])
+                                               (verbose-log! "CHORD-CHECK pending="
+                                                 (if *chord-pending-char*
+                                                     (string
+                                                       *chord-pending-char*)
+                                                     "#f")
+                                                 " key="
+                                                 (if (and is-printable
+                                                          no-ctrl
+                                                          no-alt)
+                                                     text
+                                                     "non-printable")
+                                                 " chord-start?="
+                                                 (if (and is-printable
+                                                          no-ctrl
+                                                          no-alt)
+                                                     (if (chord-start-char?
+                                                           (string-ref
+                                                             text
+                                                             0))
+                                                         "Y"
+                                                         "N")
+                                                     "N/A")
+                                                 " autorepeat="
+                                                 (if autorepeat? "Y" "N")
+                                                 " prefix="
+                                                 (if (null?
+                                                       (key-state-prefix-keys
+                                                         (app-state-key-state
+                                                           app)))
+                                                     "none"
+                                                     "active"))
+                                               (cond
+                                                 [(and *chord-pending-char*
+                                                       (not *chord-timer-fired*))
+                                                  (let* ([ch1 *chord-pending-char*]
+                                                         [saved-code *chord-pending-code*]
+                                                         [saved-mods *chord-pending-mods*]
+                                                         [saved-text *chord-pending-text*]
+                                                         [ch2 (and (= (string-length
+                                                                        text)
+                                                                      1)
+                                                                   (> (char->integer
+                                                                        (string-ref
+                                                                          text
+                                                                          0))
+                                                                      31)
+                                                                   (zero?
+                                                                     (bitwise-and
+                                                                       mods
+                                                                       QT_MOD_CTRL))
+                                                                   (zero?
+                                                                     (bitwise-and
+                                                                       mods
+                                                                       QT_MOD_ALT))
+                                                                   (string-ref
+                                                                     text
+                                                                     0))])
+                                                    (cond
+                                                      [autorepeat?
+                                                       (verbose-log!
+                                                         "CHORD-AUTOREPEAT ignored ch="
+                                                         (string ch1))
+                                                       (void)]
+                                                      [(and (not ch2)
+                                                            (or (and (>= code
+                                                                         16777248)
+                                                                     (<= code
+                                                                         16777253))
+                                                                (= code
+                                                                   16777299)
+                                                                (= code
+                                                                   16777300)
+                                                                (= code
+                                                                   16781571)))
+                                                       (verbose-log!
+                                                         "CHORD-IGNORE-MODIFIER pending="
+                                                         (string ch1)
+                                                         " code="
+                                                         (number->string
+                                                           code))
+                                                       (void)]
+                                                      [(not ch2)
+                                                       (verbose-log!
+                                                         "CHORD-CANCEL-NONCHORD pending="
+                                                         (string ch1)
+                                                         " code="
+                                                         (number->string
+                                                           code))
+                                                       (qt-timer-stop!
+                                                         *chord-timer*)
+                                                       (set! *chord-pending-char*
+                                                         #f)
+                                                       (do-normal-key!
+                                                         saved-code
+                                                         saved-mods
+                                                         saved-text)
+                                                       (do-normal-key!
+                                                         code
                                                          mods
-                                                         QT_MOD_ALT))])
-                                         (verbose-log! "CHORD-CHECK pending="
-                                           (if *chord-pending-char*
-                                               (string
-                                                 *chord-pending-char*)
-                                               "#f")
-                                           " key="
-                                           (if (and is-printable
-                                                    no-ctrl
-                                                    no-alt)
-                                               text
-                                               "non-printable")
-                                           " chord-start?="
-                                           (if (and is-printable
-                                                    no-ctrl
-                                                    no-alt)
-                                               (if (chord-start-char?
-                                                     (string-ref text 0))
-                                                   "Y"
-                                                   "N")
-                                               "N/A")
-                                           " autorepeat="
-                                           (if autorepeat? "Y" "N")
-                                           " prefix="
-                                           (if (null?
-                                                 (key-state-prefix-keys
-                                                   (app-state-key-state
-                                                     app)))
-                                               "none"
-                                               "active"))
-                                         (cond
-                                           [(and *chord-pending-char*
-                                                 (not *chord-timer-fired*))
-                                            (let* ([ch1 *chord-pending-char*]
-                                                   [saved-code *chord-pending-code*]
-                                                   [saved-mods *chord-pending-mods*]
-                                                   [saved-text *chord-pending-text*]
-                                                   [ch2 (and (= (string-length
-                                                                  text)
-                                                                1)
-                                                             (> (char->integer
-                                                                  (string-ref
-                                                                    text
-                                                                    0))
-                                                                31)
-                                                             (zero?
-                                                               (bitwise-and
+                                                         text)]
+                                                      [else
+                                                       (let ([chord-cmd (chord-lookup
+                                                                          ch1
+                                                                          ch2)])
+                                                         (verbose-log!
+                                                           "CHORD-RESOLVE ch1="
+                                                           (string ch1)
+                                                           " ch2="
+                                                           (string ch2)
+                                                           " cmd="
+                                                           (if chord-cmd
+                                                               (symbol->string
+                                                                 chord-cmd)
+                                                               "#f"))
+                                                         (qt-timer-stop!
+                                                           *chord-timer*)
+                                                         (set! *chord-pending-char*
+                                                           #f)
+                                                         (if chord-cmd
+                                                             (begin
+                                                               (execute-command!
+                                                                 app
+                                                                 chord-cmd)
+                                                               (qt-update-visual-decorations!
+                                                                 (qt-current-editor
+                                                                   (app-state-frame
+                                                                     app)))
+                                                               (qt-update-mark-selection!
+                                                                 app)
+                                                               (qt-modeline-update!
+                                                                 app)
+                                                               (qt-tabbar-update!
+                                                                 app)
+                                                               (qt-update-frame-title!
+                                                                 app)
+                                                               (qt-echo-draw!
+                                                                 (app-state-echo
+                                                                   app)
+                                                                 echo-label))
+                                                             (begin
+                                                               (do-normal-key!
+                                                                 saved-code
+                                                                 saved-mods
+                                                                 saved-text)
+                                                               (do-normal-key!
+                                                                 code
                                                                  mods
-                                                                 QT_MOD_CTRL))
-                                                             (zero?
-                                                               (bitwise-and
-                                                                 mods
-                                                                 QT_MOD_ALT))
-                                                             (string-ref
-                                                               text
-                                                               0))])
-                                              (cond
-                                                [autorepeat?
-                                                 (verbose-log!
-                                                   "CHORD-AUTOREPEAT ignored ch="
-                                                   (string ch1))
-                                                 (void)]
-                                                [(and (not ch2)
-                                                      (or (and (>= code
-                                                                   16777248)
-                                                               (<= code
-                                                                   16777253))
-                                                          (= code 16777299)
-                                                          (= code 16777300)
-                                                          (= code
-                                                             16781571)))
-                                                 (verbose-log!
-                                                   "CHORD-IGNORE-MODIFIER pending="
-                                                   (string ch1)
-                                                   " code="
-                                                   (number->string code))
-                                                 (void)]
-                                                [(not ch2)
-                                                 (verbose-log!
-                                                   "CHORD-CANCEL-NONCHORD pending="
-                                                   (string ch1)
-                                                   " code="
-                                                   (number->string code))
-                                                 (qt-timer-stop!
-                                                   *chord-timer*)
-                                                 (set! *chord-pending-char*
-                                                   #f)
-                                                 (do-normal-key!
-                                                   saved-code
-                                                   saved-mods
-                                                   saved-text)
-                                                 (do-normal-key!
-                                                   code
-                                                   mods
-                                                   text)]
-                                                [else
-                                                 (let ([chord-cmd (chord-lookup
-                                                                    ch1
-                                                                    ch2)])
-                                                   (verbose-log! "CHORD-RESOLVE ch1="
-                                                     (string ch1) " ch2="
-                                                     (string ch2) " cmd="
-                                                     (if chord-cmd
-                                                         (symbol->string
-                                                           chord-cmd)
-                                                         "#f"))
-                                                   (qt-timer-stop!
-                                                     *chord-timer*)
-                                                   (set! *chord-pending-char*
-                                                     #f)
-                                                   (if chord-cmd
-                                                       (begin
-                                                         (execute-command!
-                                                           app
-                                                           chord-cmd)
-                                                         (qt-update-visual-decorations!
-                                                           (qt-current-editor
-                                                             (app-state-frame
-                                                               app)))
-                                                         (qt-update-mark-selection!
-                                                           app)
-                                                         (qt-modeline-update!
-                                                           app)
-                                                         (qt-tabbar-update!
-                                                           app)
-                                                         (qt-update-frame-title!
-                                                           app)
-                                                         (qt-echo-draw!
-                                                           (app-state-echo
-                                                             app)
-                                                           echo-label))
-                                                       (begin
-                                                         (do-normal-key!
-                                                           saved-code
-                                                           saved-mods
-                                                           saved-text)
-                                                         (do-normal-key!
-                                                           code
+                                                                 text))))]))]
+                                                 [(and (not autorepeat?)
+                                                       (= (string-length
+                                                            text)
+                                                          1)
+                                                       (> (char->integer
+                                                            (string-ref
+                                                              text
+                                                              0))
+                                                          31)
+                                                       (zero?
+                                                         (bitwise-and
                                                            mods
-                                                           text))))]))]
-                                           [(and (not autorepeat?)
-                                                 (= (string-length text) 1)
-                                                 (> (char->integer
+                                                           QT_MOD_CTRL))
+                                                       (zero?
+                                                         (bitwise-and
+                                                           mods
+                                                           QT_MOD_ALT))
+                                                       (null?
+                                                         (key-state-prefix-keys
+                                                           (app-state-key-state
+                                                             app)))
+                                                       (chord-start-char?
+                                                         (string-ref
+                                                           text
+                                                           0)))
+                                                  (verbose-log! "CHORD-PENDING ch="
+                                                    (string
                                                       (string-ref text 0))
-                                                    31)
-                                                 (zero?
-                                                   (bitwise-and
-                                                     mods
-                                                     QT_MOD_CTRL))
-                                                 (zero?
-                                                   (bitwise-and
-                                                     mods
-                                                     QT_MOD_ALT))
-                                                 (null?
-                                                   (key-state-prefix-keys
-                                                     (app-state-key-state
-                                                       app)))
-                                                 (chord-start-char?
-                                                   (string-ref text 0)))
-                                            (verbose-log! "CHORD-PENDING ch="
-                                              (string (string-ref text 0))
-                                              " timeout="
-                                              (number->string
-                                                *chord-timeout*)
-                                              "ms")
-                                            (set! *chord-pending-char*
-                                              (string-ref text 0))
-                                            (set! *chord-pending-code*
-                                              code)
-                                            (set! *chord-pending-mods*
-                                              mods)
-                                            (set! *chord-pending-text*
-                                              text)
-                                            (set! *chord-timer-fired* #f)
-                                            (qt-timer-start!
-                                              *chord-timer*
-                                              *chord-timeout*)]
-                                           [else
-                                            (do-normal-key!
-                                              code
-                                              mods
-                                              text)])))]))))])
+                                                    " timeout="
+                                                    (number->string
+                                                      *chord-timeout*)
+                                                    "ms")
+                                                  (set! *chord-pending-char*
+                                                    (string-ref text 0))
+                                                  (set! *chord-pending-code*
+                                                    code)
+                                                  (set! *chord-pending-mods*
+                                                    mods)
+                                                  (set! *chord-pending-text*
+                                                    text)
+                                                  (set! *chord-timer-fired*
+                                                    #f)
+                                                  (qt-timer-start!
+                                                    *chord-timer*
+                                                    *chord-timeout*)]
+                                                 [else
+                                                  (do-normal-key!
+                                                    code
+                                                    mods
+                                                    text)])))))]))))])
            (qt-on-key-press-consuming!
              (qt-current-editor fr)
              key-handler)
diff --git a/lib/jerboa-emacs/qt/commands-config.sls b/lib/jerboa-emacs/qt/commands-config.sls
index b9ac435..12334d2 100644
--- a/lib/jerboa-emacs/qt/commands-config.sls
+++ b/lib/jerboa-emacs/qt/commands-config.sls
@@ -18,10 +18,11 @@
    *qt-profiler-running* *qt-profiler-start-stats*
    *profiler-data* cmd-profiler-start cmd-profiler-stop
    cmd-show-tab-count cmd-show-trailing-whitespace-count
-   terminal-buffer-counter cmd-term cmd-terminal-send
-   cmd-term-interrupt cmd-term-send-eof cmd-term-send-tab
-   cmd-multi-vterm *terminal-copy-mode* cmd-vterm-copy-mode
-   cmd-vterm-copy-done get-terminal-buffers
+   *SCI_SETCODEPAGE* *SC_CP_UTF8* qt-insert-prompt!
+   terminal-buffer-counter *terminal-widget-map* cmd-term
+   cmd-terminal-send cmd-term-interrupt cmd-term-send-eof
+   cmd-term-send-tab cmd-multi-vterm *terminal-copy-mode*
+   cmd-vterm-copy-mode cmd-vterm-copy-done get-terminal-buffers
    qt-switch-to-terminal! cmd-term-list cmd-term-next
    cmd-term-prev cmd-ediff-files cmd-comment-dwim)
   (import
@@ -854,10 +855,49 @@
            (string-append
              (number->string count)
              " lines with trailing whitespace"))))
+  (define *SCI_SETCODEPAGE*--cell (vector 2037))
+  (define *SC_CP_UTF8*--cell (vector 65001))
+  (def (qt-insert-prompt! ed ts)
+       "Insert PS1 prompt with ANSI colors using Qt Scintilla APIs.\n   Uses sci-send/SCI_APPENDTEXT (not TUI editor-append-text).\n   Returns character count of document after insertion (for prompt-pos tracking).\n   NOTE: must return CHARACTERS (not bytes) — prompt-pos is compared against\n   string-length which is char-based. UTF-8 multi-byte chars (like PS1 arrows)\n   make byte-offset != char-count."
+       (let* ([raw (terminal-prompt-raw ts)]
+              [clean (list->string
+                       (filter
+                         (lambda (c)
+                           (not (or (char=? c (integer->char 1))
+                                    (char=? c (integer->char 2)))))
+                         (string->list raw)))]
+              [segments (parse-ansi-segments clean)])
+         (let loop ([segs segments]
+                    [pos (sci-send ed SCI_GETLENGTH)])
+           (if (null? segs)
+               (string-length (qt-plain-text-edit-text ed))
+               (let* ([seg (car segs)]
+                      [text (text-segment-text seg)]
+                      [fg (text-segment-fg-color seg)]
+                      [bold? (text-segment-bold? seg)]
+                      [style (color-to-style fg bold?)]
+                      [utf8-bv (string->utf8 text)]
+                      [text-len (bytevector-length utf8-bv)]
+                      [utf8-str (let loop2 ([i (- text-len 1)] [acc '()])
+                                  (if (< i 0)
+                                      (list->string acc)
+                                      (loop2
+                                        (- i 1)
+                                        (cons
+                                          (integer->char
+                                            (bytevector-u8-ref utf8-bv i))
+                                          acc))))])
+                 (sci-send/string ed SCI_APPENDTEXT utf8-str text-len)
+                 (when (> style 0)
+                   (sci-send ed SCI_STARTSTYLING pos 0)
+                   (sci-send ed SCI_SETSTYLING text-len style))
+                 (loop (cdr segs) (+ pos text-len)))))))
   (define terminal-buffer-counter--cell (vector 0))
+  (define *terminal-widget-map*--cell
+    (vector (make-hash-table-eq)))
   (def (cmd-term app)
-       "Open a new gsh-backed terminal buffer."
-       (verbose-log! "cmd-term: begin")
+       "Open a QTerminalWidget-backed terminal buffer.\n   Uses libvterm for proper VT100 terminal emulation with full color support."
+       (verbose-log! "cmd-term: begin (QTerminalWidget)")
        (let* ([fr (app-state-frame app)]
               [ed (current-qt-editor app)]
               [name (begin
@@ -871,38 +911,34 @@
                             "*")))]
               [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")
          (qt-edit-window-buffer-set! (qt-current-window fr) buf)
          (with-catch
            (lambda (e)
              (let ([msg (with-output-to-string
                           (lambda () (display-exception e)))])
-               (jemacs-log! "cmd-term: gsh init failed: " msg)
-               (verbose-log! "cmd-term: gsh init FAILED: " msg)
+               (jemacs-log! "cmd-term: QTerminalWidget failed: " msg)
+               (verbose-log! "cmd-term: QTerminalWidget FAILED: " msg)
                (echo-error!
                  (app-state-echo app)
                  (string-append "Terminal failed: " msg))))
            (lambda ()
-             (verbose-log! "cmd-term: terminal-start! begin")
-             (let ([ts (terminal-start!)])
-               (verbose-log! "cmd-term: terminal-start! done")
-               (hash-put! *terminal-state* buf ts)
-               (let ([prompt (terminal-prompt ts)])
-                 (verbose-log!
-                   "cmd-term: qt-plain-text-edit-set-text! begin prompt-len="
-                   (number->string (string-length prompt)))
-                 (qt-plain-text-edit-set-text! ed prompt)
-                 (verbose-log!
-                   "cmd-term: qt-plain-text-edit-set-text! done")
-                 (terminal-state-prompt-pos-set! ts (string-length prompt))
-                 (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END)))
-             (verbose-log! "cmd-term: done, terminal started")
-             (echo-message!
-               (app-state-echo app)
-               (string-append name " started"))))))
+             (let* ([win (qt-current-window fr)]
+                    [container (qt-edit-window-container win)]
+                    [term (qt-terminal-create container)])
+               (qt-terminal-set-font! term "DejaVu Sans Mono" 11)
+               (qt-terminal-set-colors! term 12305103 2632756)
+               (let ([idx (qt-stacked-widget-add-widget!
+                            container
+                            (qt-terminal-widget term))])
+                 (qt-stacked-widget-set-current-index! container idx))
+               (hash-put! *terminal-widget-map* buf term)
+               (qt-terminal-spawn! term "")
+               (qt-terminal-focus! term)
+               (verbose-log! "cmd-term: QTerminalWidget spawned")
+               (echo-message!
+                 (app-state-echo app)
+                 (string-append name " started")))))))
   (def (cmd-terminal-send app)
        "Execute the current input line in the terminal via gsh.\n   Builtins run synchronously, external commands run async via PTY.\n   When PTY is busy (e.g. sudo password prompt), sends newline to PTY."
        (let* ([buf (current-qt-buffer app)]
@@ -978,15 +1014,11 @@
                                   #\newline)
                           (qt-plain-text-edit-insert-text! ed "\n")))
                       (when (hash-get *terminal-state* buf)
-                        (let ([prompt (terminal-prompt ts)])
-                          (qt-plain-text-edit-move-cursor!
-                            ed
-                            QT_CURSOR_END)
-                          (qt-plain-text-edit-insert-text! ed prompt)
-                          (terminal-state-prompt-pos-set!
-                            ts
-                            (string-length (qt-plain-text-edit-text ed)))
-                          (qt-plain-text-edit-ensure-cursor-visible! ed)))]
+                        (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END)
+                        (terminal-state-prompt-pos-set!
+                          ts
+                          (qt-insert-prompt! ed ts))
+                        (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)]
@@ -994,12 +1026,10 @@
                       (cond
                         [(eq? output 'clear)
                          (qt-plain-text-edit-set-text! ed "")
-                         (let ([prompt (terminal-prompt ts)])
-                           (qt-plain-text-edit-insert-text! ed prompt)
-                           (terminal-state-prompt-pos-set!
-                             ts
-                             (string-length (qt-plain-text-edit-text ed)))
-                           (qt-plain-text-edit-ensure-cursor-visible! ed))]
+                         (terminal-state-prompt-pos-set!
+                           ts
+                           (qt-insert-prompt! ed ts))
+                         (qt-plain-text-edit-ensure-cursor-visible! ed)]
                         [(eq? output 'exit)
                          (terminal-stop! ts)
                          (let* ([fr (app-state-frame app)]
@@ -1048,14 +1078,12 @@
               (qt-plain-text-edit-insert-text! ed "^C\n")
               (qt-plain-text-edit-ensure-cursor-visible! ed))]
            [else
-            (let* ([ed (current-qt-editor app)]
-                   [prompt (terminal-prompt ts)])
+            (let ([ed (current-qt-editor app)])
               (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END)
               (qt-plain-text-edit-insert-text! ed "^C\n")
-              (qt-plain-text-edit-insert-text! ed prompt)
               (terminal-state-prompt-pos-set!
                 ts
-                (string-length (qt-plain-text-edit-text ed)))
+                (qt-insert-prompt! ed ts))
               (qt-plain-text-edit-ensure-cursor-visible! ed))])))
   (def (cmd-term-send-eof app)
        "Close the terminal/shell/eshell buffer (Ctrl-D).\n   Only exits if the current input line is empty (standard shell behavior)."
@@ -1364,6 +1392,17 @@
     (identifier-syntax
       [id (vector-ref *profiler-data*--cell 0)]
       [(set! id val) (vector-set! *profiler-data*--cell 0 val)]))
+  (define-syntax *SCI_SETCODEPAGE*
+    (identifier-syntax
+      [id (vector-ref *SCI_SETCODEPAGE*--cell 0)]
+      [(set! id val) (vector-set!
+                       *SCI_SETCODEPAGE*--cell
+                       0
+                       val)]))
+  (define-syntax *SC_CP_UTF8*
+    (identifier-syntax
+      [id (vector-ref *SC_CP_UTF8*--cell 0)]
+      [(set! id val) (vector-set! *SC_CP_UTF8*--cell 0 val)]))
   (define-syntax terminal-buffer-counter
     (identifier-syntax
       [id (vector-ref terminal-buffer-counter--cell 0)]
@@ -1371,6 +1410,13 @@
                        terminal-buffer-counter--cell
                        0
                        val)]))
+  (define-syntax *terminal-widget-map*
+    (identifier-syntax
+      [id (vector-ref *terminal-widget-map*--cell 0)]
+      [(set! id val) (vector-set!
+                       *terminal-widget-map*--cell
+                       0
+                       val)]))
   (define-syntax *terminal-copy-mode*
     (identifier-syntax
       [id (vector-ref *terminal-copy-mode*--cell 0)]
diff --git a/lib/jerboa-emacs/qt/commands-edit.sls b/lib/jerboa-emacs/qt/commands-edit.sls
index 4072337..18c15c8 100644
--- a/lib/jerboa-emacs/qt/commands-edit.sls
+++ b/lib/jerboa-emacs/qt/commands-edit.sls
@@ -741,7 +741,7 @@
                (qt-edit-window-buffer-set! (qt-current-window fr) buf)
                (gsh-eshell-init-buffer! buf)
                (let ([welcome (string-append
-                                "gsh — Gerbil Shell\n"
+                                "gsh — Jerboa Shell\n"
                                 "Type commands or 'exit' to close.\n\n"
                                 (gsh-eshell-get-prompt buf))])
                  (qt-plain-text-edit-set-text! ed welcome)
diff --git a/lib/jerboa-emacs/qt/commands-parity.sls b/lib/jerboa-emacs/qt/commands-parity.sls
index 1fcd76b..2f92ec1 100644
--- a/lib/jerboa-emacs/qt/commands-parity.sls
+++ b/lib/jerboa-emacs/qt/commands-parity.sls
@@ -1087,7 +1087,7 @@
   (def (cmd-help-with-tutorial app)
        "Show the jemacs tutorial (C-h t)."
        (let* ([text (string-append "=== Jemacs Tutorial ===\n\n"
-                     "Welcome to Jemacs, a Gerbil Scheme Emacs replacement.\n\n"
+                     "Welcome to Jemacs, a Jerboa Scheme Emacs replacement.\n\n"
                      "== Basic Movement ==\n"
                      "  C-f / C-b    Forward / backward character\n"
                      "  M-f / M-b    Forward / backward word\n"
@@ -1131,7 +1131,7 @@
                      "  M-x magit-status   Git integration\n"
                      "  M-x treemacs       File tree\n"
                      "  M-x shell          Shell\n"
-                     "  M-x eshell         Gerbil shell\n"
+                     "  M-x eshell         Jerboa shell\n"
                      "  M-x term           Terminal\n")]
               [fr (app-state-frame app)]
               [ed (current-qt-editor app)]
diff --git a/lib/jerboa-emacs/qt/commands-parity2.sls b/lib/jerboa-emacs/qt/commands-parity2.sls
index 431b1d3..71da104 100644
--- a/lib/jerboa-emacs/qt/commands-parity2.sls
+++ b/lib/jerboa-emacs/qt/commands-parity2.sls
@@ -168,7 +168,7 @@
        "Switch to CSS mode with syntax highlighting."
        (qt-set-major-mode! app 'css "CSS mode"))
   (def (cmd-scheme-mode app)
-       "Switch to Scheme/Gerbil mode with syntax highlighting."
+       "Switch to Scheme/Jerboa mode with syntax highlighting."
        (qt-set-major-mode! app 'scheme "Scheme mode"))
   (def (cmd-check-parens app)
        "Check for unbalanced parentheses in the current buffer."
diff --git a/lib/jerboa-emacs/qt/commands-parity5.sls b/lib/jerboa-emacs/qt/commands-parity5.sls
index 7705dbc..768fcff 100644
--- a/lib/jerboa-emacs/qt/commands-parity5.sls
+++ b/lib/jerboa-emacs/qt/commands-parity5.sls
@@ -1181,7 +1181,7 @@
                      (sci-send ed SCI_SETREADONLY 1)
                      (qt-plain-text-edit-set-cursor-position! ed 0))))))))
   (def (cmd-package-list-packages app)
-       "List installed Gerbil packages."
+       "List installed Jerboa packages."
        (let* ([output (qt-run-gerbil-pkg '("list"))]
               [fr (app-state-frame app)]
               [win (qt-current-window fr)]
@@ -1191,11 +1191,11 @@
          (qt-edit-window-buffer-set! win buf)
          (qt-plain-text-edit-set-text!
            ed
-           (string-append "Installed Gerbil Packages\n\n" output "\n"))
+           (string-append "Installed Jerboa Packages\n\n" output "\n"))
          (sci-send ed SCI_SETREADONLY 1)
          (qt-plain-text-edit-set-cursor-position! ed 0)))
   (def (cmd-package-install app)
-       "Install a Gerbil package by name."
+       "Install a Jerboa package by name."
        (let ([pkg (qt-echo-read-string
                     app
                     "Package to install: ")])
@@ -1208,7 +1208,7 @@
                (app-state-echo app)
                (string-append "Install: " result))))))
   (def (cmd-package-delete app)
-       "Uninstall a Gerbil package."
+       "Uninstall a Jerboa package."
        (let ([pkg (qt-echo-read-string app "Package to remove: ")])
          (when (and pkg (> (string-length pkg) 0))
            (let ([result (qt-run-gerbil-pkg (list "uninstall" pkg))])
@@ -1216,7 +1216,7 @@
                (app-state-echo app)
                (string-append "Uninstall: " result))))))
   (def (cmd-package-refresh-contents app)
-       "Refresh Gerbil package list."
+       "Refresh Jerboa package list."
        (echo-message! (app-state-echo app) "Updating packages...")
        (let ([result (qt-run-gerbil-pkg '("update"))])
          (echo-message!
@@ -1229,7 +1229,7 @@
               [ed (qt-edit-window-editor win)]
               [buf (qt-buffer-create! "*Package Archives*" ed #f)]
               [text (string-append "Package Archives\n" (make-string 40 #\=) "\n\n"
-                      "Gerbil packages are managed via gerbil pkg.\n\n"
+                      "Jerboa packages are managed via gerbil pkg.\n\n"
                       "Commands:\n"
                       "  M-x package-install          Install a package\n"
                       "  M-x package-delete           Remove a package\n"
diff --git a/lib/jerboa-emacs/qt/commands-search.sls b/lib/jerboa-emacs/qt/commands-search.sls
index df73aff..963c70a 100644
--- a/lib/jerboa-emacs/qt/commands-search.sls
+++ b/lib/jerboa-emacs/qt/commands-search.sls
@@ -429,7 +429,7 @@
              (compilation-run-command! app cmd))))
   (define *compile-on-save*--cell (vector #f))
   (def (compile-on-save-check! app path)
-       "If compile-on-save is enabled and PATH is a .ss file in a Gerbil project,\n   run gerbil build in the background."
+       "If compile-on-save is enabled and PATH is a .ss file in a Jerboa project,\n   run gerbil build in the background."
        (when (and *compile-on-save*
                   path
                   (string-suffix? ".ss" path))
@@ -642,7 +642,7 @@
             (reverse errors))]
          [else (flycheck-parse-gcc-format output file)]))
   (def (flycheck-check! app path)
-       "Run linter async on a source file, parse errors. Updates *flycheck-errors*.\nSupports Gerbil (.ss), Python, JS/TS, Go, Shell, C/C++, Ruby."
+       "Run linter async on a source file, parse errors. Updates *flycheck-errors*.\nSupports Jerboa (.ss), Python, JS/TS, Go, Shell, C/C++, Ruby."
        (when (and *flycheck-mode* path)
          (cond
            [(string-suffix? ".ss" path)
@@ -748,7 +748,7 @@
            lines)
          (reverse errors)))
   (def (cmd-flycheck-mode app)
-       "Toggle flycheck (live syntax checking on save) for Gerbil files."
+       "Toggle flycheck (live syntax checking on save) for Jerboa files."
        (set! *flycheck-mode* (not *flycheck-mode*))
        (when (not *flycheck-mode*)
          (let ([ed (current-qt-editor app)])
@@ -823,7 +823,7 @@
              (qt-text-document-set-modified! (buffer-doc-pointer buf) #f)
              (qt-plain-text-edit-set-cursor-position! ed 0))))
   (def (cmd-toggle-compile-on-save app)
-       "Toggle automatic compilation when saving Gerbil files."
+       "Toggle automatic compilation when saving Jerboa files."
        (set! *compile-on-save* (not *compile-on-save*))
        (echo-message!
          (app-state-echo app)
@@ -1042,7 +1042,7 @@
            (current-qt-editor app)
            date-str)))
   (def (cmd-eval-buffer app)
-       "Evaluate all top-level forms in the current buffer.\n   Full Gerbil syntax supported (def, defstruct, hash, match, etc.)."
+       "Evaluate all top-level forms in the current buffer.\n   Full Jerboa syntax supported (def, defstruct, hash, match, etc.)."
        (let* ([echo (app-state-echo app)]
               [ed (current-qt-editor app)]
               [buf (current-qt-buffer app)]
diff --git a/lib/jerboa-emacs/qt/commands-shell.sls b/lib/jerboa-emacs/qt/commands-shell.sls
index b9fd3e4..d86ef94 100644
--- a/lib/jerboa-emacs/qt/commands-shell.sls
+++ b/lib/jerboa-emacs/qt/commands-shell.sls
@@ -1440,7 +1440,7 @@
                             (app-state-echo app)
                             (string-join shown "  ")))]))))))))
   (def (cmd-completion-at-point app)
-       "Smart completion at point. For Gerbil buffers, combines buffer words\n   with known Gerbil standard library symbols."
+       "Smart completion at point. For Jerboa buffers, combines buffer words\n   with known Jerboa standard library symbols."
        (let* ([ed (current-qt-editor app)]
               [buf (current-qt-buffer app)]
               [lang (buffer-lexer-lang buf)]
@@ -1570,10 +1570,10 @@
        "Show Emacs manual via Info."
        (info-read-topic! app "emacs"))
   (def (cmd-info-elisp-manual app)
-       "Show Gerbil documentation."
+       "Show Jerboa documentation."
        (echo-message!
          (app-state-echo app)
-         "Gerbil Scheme documentation at https://cons.io"))
+         "Jerboa Scheme documentation at https://jerboa-lang.org"))
   (def (cmd-report-bug app)
        "Report a bug."
        (echo-message!
diff --git a/lib/jerboa-emacs/qt/commands-shell2.sls b/lib/jerboa-emacs/qt/commands-shell2.sls
index eacafe7..5f7c743 100644
--- a/lib/jerboa-emacs/qt/commands-shell2.sls
+++ b/lib/jerboa-emacs/qt/commands-shell2.sls
@@ -533,7 +533,7 @@
                  (qt-plain-text-edit-ensure-cursor-visible! ed)))
              (echo-message! (app-state-echo app) "Xref: forward"))))
   (def (cmd-eldoc-mode app)
-       "Toggle eldoc mode — shows function signatures in echo area on idle.\nWhen enabled, displays the signature of the enclosing function for\nScheme/Gerbil/Lisp buffers. Also used by LSP for hover information."
+       "Toggle eldoc mode — shows function signatures in echo area on idle.\nWhen enabled, displays the signature of the enclosing function for\nScheme/Jerboa/Lisp buffers. Also used by LSP for hover information."
        (set! *eldoc-mode* (not *eldoc-mode*))
        (echo-message!
          (app-state-echo app)
diff --git a/lib/jerboa-emacs/qt/commands-vcs.sls b/lib/jerboa-emacs/qt/commands-vcs.sls
index 652be33..b8692b7 100644
--- a/lib/jerboa-emacs/qt/commands-vcs.sls
+++ b/lib/jerboa-emacs/qt/commands-vcs.sls
@@ -1376,7 +1376,7 @@
   (define *gerbil-signatures*--cell
     (vector (make-hash-table)))
   (def (eldoc-init-signatures!)
-   "Populate the signature database with common Gerbil functions."
+   "Populate the signature database with common Jerboa functions."
    (hash-put!
      *gerbil-signatures*
      "hash-get"
diff --git a/lib/jerboa-emacs/qt/commands.sls b/lib/jerboa-emacs/qt/commands.sls
index 7c032a4..2574a49 100644
--- a/lib/jerboa-emacs/qt/commands.sls
+++ b/lib/jerboa-emacs/qt/commands.sls
@@ -4,34 +4,34 @@
 
 (library (jerboa-emacs qt commands)
   (export qt-register-all-commands! dired-open-directory!
-   qt-open-image-inline! *qt-app-ptr* qt-kill-ring-push!
-   *isearch-active* isearch-handle-key! *qreplace-active*
-   qreplace-handle-key! recent-files-add! recent-files-load!
-   bookmarks-load! session-save! session-restore-files
-   *tab-bar-visible* *auto-revert-tail-buffers* *file-mtimes*
-   file-mtime-record! file-mtime-changed? *eldoc-mode*
-   eldoc-display! *current-theme* *themes* theme-stylesheet
-   load-theme! apply-theme! load-theme define-theme!
-   buffer-touch! custom-keys-load! abbrevs-load!
-   load-init-file! scratch-save! scratch-restore!
-   scratch-update-text! undo-history-record! winner-save!
-   *follow-mode* *view-mode-buffers* *so-long-buffers*
-   check-so-long! savehist-save! savehist-load!
-   gsh-history-save! gsh-history-load! save-place-load!
-   save-place-save! auto-fill-check!
-   *auto-save-disabled-buffers* *require-final-newline*
-   *save-place-enabled* *centered-cursor-mode*
-   uniquify-buffer-name! shell-command-to-string
-   shell-command-to-buffer! register-shell-command!
-   *user-shell-commands* workspace-init! *workspaces*
-   *current-workspace* workspace-add-buffer!
-   workspace-remove-buffer! qt-pulse-line! qt-pulse-tick!
-   qt-pulse-check-jump! qt-idle-highlight-symbol!
-   *diff-hl-active* *qt-describe-key-pending*
-   qt-describe-key-result! *qt-quoted-insert-pending*
-   qt-quoted-insert-handle! qt-record-edit-position!
-   *qt-desktop-save-mode* qt-aggressive-indent-line!
-   *snake-active* snake-handle-key!)
+   qt-open-image-inline! *qt-app-ptr* *terminal-widget-map*
+   qt-kill-ring-push! *isearch-active* isearch-handle-key!
+   *qreplace-active* qreplace-handle-key! recent-files-add!
+   recent-files-load! bookmarks-load! session-save!
+   session-restore-files *tab-bar-visible*
+   *auto-revert-tail-buffers* *file-mtimes* file-mtime-record!
+   file-mtime-changed? *eldoc-mode* eldoc-display!
+   *current-theme* *themes* theme-stylesheet load-theme!
+   apply-theme! load-theme define-theme! buffer-touch!
+   custom-keys-load! abbrevs-load! load-init-file!
+   scratch-save! scratch-restore! scratch-update-text!
+   undo-history-record! winner-save! *follow-mode*
+   *view-mode-buffers* *so-long-buffers* check-so-long!
+   savehist-save! savehist-load! gsh-history-save!
+   gsh-history-load! save-place-load! save-place-save!
+   auto-fill-check! *auto-save-disabled-buffers*
+   *require-final-newline* *save-place-enabled*
+   *centered-cursor-mode* uniquify-buffer-name!
+   shell-command-to-string shell-command-to-buffer!
+   register-shell-command! *user-shell-commands*
+   workspace-init! *workspaces* *current-workspace*
+   workspace-add-buffer! workspace-remove-buffer!
+   qt-pulse-line! qt-pulse-tick! qt-pulse-check-jump!
+   qt-idle-highlight-symbol! *diff-hl-active*
+   *qt-describe-key-pending* qt-describe-key-result!
+   *qt-quoted-insert-pending* qt-quoted-insert-handle!
+   qt-record-edit-position! *qt-desktop-save-mode*
+   qt-aggressive-indent-line! *snake-active* snake-handle-key!)
   (import
    (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
      getenv path-extension path-absolute? thread? make-mutex
@@ -678,6 +678,47 @@
                  (echo-error!
                    echo
                    (string-append "No buffer: " target-name)))))))
+  (def (cmd-kill-buffer-force app)
+       "Kill current buffer immediately, no prompts. Switches to another buffer first.\n   Used by stress tests and automation where echo-area interaction is not possible."
+       (let* ([echo (app-state-echo app)]