Share Qt vterm sessions across split windows

ober

c3d067292a010b0d2f0f53c7e986deefccc8a371

diff --git a/lib/jerboa-emacs/qt/app.sls b/lib/jerboa-emacs/qt/app.sls
index e288353..99f8b75 100644
--- a/lib/jerboa-emacs/qt/app.sls
+++ b/lib/jerboa-emacs/qt/app.sls
@@ -10,11 +10,11 @@
      mutex? mutex-name)
    (std sugar) (std misc string) (jerboa-scintilla constants)
    (jerboa-emacs qt sci-shim)
-   (except (jerboa-qt qt) QT_MOD_NONE QT_MOD_SHIFT QT_MOD_CONTROL
-    QT_MOD_ALT QT_MOD_META QT_KEY_ESCAPE QT_KEY_BACKSPACE
-    QT_KEY_RETURN QT_KEY_ENTER QT_KEY_DELETE QT_KEY_TAB
-    QT_KEY_BACKTAB QT_KEY_INSERT QT_KEY_HOME QT_KEY_END
-    QT_KEY_LEFT QT_KEY_RIGHT QT_KEY_UP QT_KEY_DOWN
+   (except (jerboa-qt qt) QT_MOD_NONE QT_MOD_SHIFT
+    QT_MOD_CONTROL QT_MOD_ALT QT_MOD_META QT_KEY_ESCAPE
+    QT_KEY_BACKSPACE QT_KEY_RETURN QT_KEY_ENTER QT_KEY_DELETE
+    QT_KEY_TAB QT_KEY_BACKTAB QT_KEY_INSERT QT_KEY_HOME
+    QT_KEY_END QT_KEY_LEFT QT_KEY_RIGHT QT_KEY_UP QT_KEY_DOWN
     QT_KEY_PAGE_UP QT_KEY_PAGE_DOWN QT_KEY_SPACE QT_KEY_F1
     QT_KEY_F2 QT_KEY_F3 QT_KEY_F4 QT_KEY_F5 QT_KEY_F6 QT_KEY_F7
     QT_KEY_F8 QT_KEY_F9 QT_KEY_F10 QT_KEY_F11 QT_KEY_F12
@@ -775,8 +775,7 @@
            "commands registered: "
            (number->string (hash-length *all-commands*))
            " total")
-         (let ([image-key-installed (make-hash-table-eq)]
-               [terminal-key-installed (make-hash-table-eq)])
+         (let ([image-key-installed (make-hash-table-eq)])
            (add-hook!
              'post-buffer-attach-hook
              (lambda (editor buf)
@@ -789,24 +788,23 @@
                  (lambda ()
                    (cond
                      [(hash-get *terminal-widget-map* buf) =>
-                      (lambda (term)
+                      (lambda (session)
                         (let ([win (hash-get *editor-window-map* editor)])
                           (when win
                             (let* ([container (qt-edit-window-container
                                                 win)]
-                                   [count (qt-stacked-widget-count
-                                            container)]
-                                   [tw (qt-terminal-widget term)])
-                              (unless (hash-get terminal-key-installed tw)
-                                ((app-state-key-handler app) tw)
-                                (hash-put! terminal-key-installed tw #t))
-                              (if (> count 1)
-                                  (begin
-                                    (qt-stacked-widget-set-current-widget!
-                                      container
-                                      tw)
-                                    (qt-widget-set-focus! tw))
-                                  (qt-widget-set-focus! editor))))))]
+                                   [view (terminal-ensure-view!
+                                           app
+                                           buf
+                                           session
+                                           container)])
+                              (qt-stacked-widget-set-current-widget!
+                                container
+                                view)
+                              (when (eq? win
+                                         (qt-current-window
+                                           (app-state-frame app)))
+                                (qt-terminal-view-focus! view))))))]
                      [(image-buffer? buf)
                       (qt-show-image-buffer! editor buf)
                       (let ([win (hash-get *editor-window-map* editor)])
@@ -1579,10 +1577,21 @@
                                                               qt-buf))]
                                               [key-src-widget (qt-last-key-widget)]
                                               [key-from-terminal? (and qt-term
-                                                                       (equal?
-                                                                         key-src-widget
-                                                                         (qt-terminal-widget
-                                                                           qt-term)))])
+                                                                       (let loop ([views (or (hash-get
+                                                                                               *terminal-views-map*
+                                                                                               qt-buf)
+                                                                                             '())])
+                                                                         (cond
+                                                                           [(null?
+                                                                              views)
+                                                                            #f]
+                                                                           [(equal?
+                                                                              key-src-widget
+                                                                              (cdr (car views)))
+                                                                            #t]
+                                                                           [else
+                                                                            (loop
+                                                                              (cdr views))])))])
                                          (if (and qt-term
                                                   key-from-terminal?
                                                   (null?
@@ -1839,30 +1848,52 @@
                (qt-on-key-press-consuming! editor key-handler)))
            (automation-set-key-target-fn!
              (lambda (fr)
-               (let* ([buf (qt-current-buffer fr)]
-                      [term (and buf
-                                 (hash-get *terminal-widget-map* buf))])
-                 (if term
-                     (qt-terminal-widget term)
-                     (qt-current-editor fr)))))
+               (let* ([win (qt-current-window fr)]
+                      [buf (qt-edit-window-buffer win)]
+                      [view (and buf
+                                 (hash-get *terminal-widget-map* buf)
+                                 (terminal-view-for-container
+                                   buf
+                                   (qt-edit-window-container win)))])
+                 (or view (qt-current-editor fr)))))
            (qt-window-set-pre-container-destroy-fn!
              (lambda (container)
-               (let ([bufs-to-remove '()])
+               (let ([entries '()])
                  (hash-for-each
-                   (lambda (buf stored-container)
-                     (when (equal? stored-container container)
-                       (let ([term (hash-get *terminal-widget-map* buf)])
-                         (when term
-                           (with-catch
-                             (lambda (e) #f)
-                             (lambda () (qt-terminal-destroy! term)))))
-                       (set! bufs-to-remove (cons buf bufs-to-remove))))
-                   *terminal-container-map*)
+                   (lambda (buf views)
+                     (set! entries (cons (cons buf views) entries)))
+                   *terminal-views-map*)
                  (for-each
-                   (lambda (buf)
-                     (hash-remove! *terminal-widget-map* buf)
-                     (hash-remove! *terminal-container-map* buf))
-                   bufs-to-remove)))))
+                   (lambda (e)
+                     (let* ([buf (car e)]
+                            [views (cdr e)]
+                            [mine (filter
+                                    (lambda (cv)
+                                      (equal? (car cv) container))
+                                    views)]
+                            [rest (filter
+                                    (lambda (cv)
+                                      (not (equal? (car cv) container)))
+                                    views)])
+                       (unless (null? mine)
+                         (for-each
+                           (lambda (cv)
+                             (with-catch
+                               (lambda (e) #f)
+                               (lambda ()
+                                 (qt-terminal-view-destroy! (cdr cv)))))
+                           mine)
+                         (hash-put! *terminal-views-map* buf rest))))
+                   entries)))))
+         (qt-window-set-post-split-focus-fn!
+           (lambda (new-win)
+             (let* ([buf (qt-edit-window-buffer new-win)]
+                    [session (and buf
+                                  (hash-get *terminal-widget-map* buf))])
+               (and session
+                    (terminal-view-for-container
+                      buf
+                      (qt-edit-window-container new-win))))))
          (schedule-periodic!
            'repl-poll
            50
@@ -2577,19 +2608,16 @@
                     (let* ([fr (app-state-frame app)]
                            [win (qt-current-window fr)]
                            [buf (qt-edit-window-buffer win)]
-                           [term (and buf
-                                      (hash-get
-                                        *terminal-widget-map*
-                                        buf))])
-                      (and term
-                           (let* ([container (qt-edit-window-container
-                                               win)]
-                                  [count (qt-stacked-widget-count
-                                           container)])
-                             (and (> count 1)
-                                  (> (qt-stacked-widget-current-index
-                                       container)
-                                     0)))))))
+                           [session (and buf
+                                         (hash-get
+                                           *terminal-widget-map*
+                                           buf))]
+                           [container (qt-edit-window-container win)])
+                      (and session
+                           (terminal-view-for-container buf container)
+                           (> (qt-stacked-widget-current-index container)
+                              0)
+                           #t))))
                 (cons
                   'test-reset!
                   (lambda ()
@@ -2597,17 +2625,15 @@
                     (let ([term-bufs (hash-keys *terminal-widget-map*)])
                       (for-each
                         (lambda (buf)
-                          (let ([term (hash-get
-                                        *terminal-widget-map*
-                                        buf)])
-                            (when term
+                          (let ([session (hash-get
+                                           *terminal-widget-map*
+                                           buf)])
+                            (when session
                               (with-catch
                                 (lambda (e) #f)
-                                (lambda () (qt-terminal-destroy! term)))
+                                (lambda () (qt-terminal-destroy! session)))
                               (hash-remove! *terminal-widget-map* buf)
-                              (hash-remove!
-                                *terminal-container-map*
-                                buf))))
+                              (hash-remove! *terminal-views-map* buf))))
                         term-bufs))
                     (when (> (length
                                (qt-frame-windows (app-state-frame app)))
diff --git a/lib/jerboa-emacs/qt/commands-config.sls b/lib/jerboa-emacs/qt/commands-config.sls
index cc358e0..a4da9b0 100644
--- a/lib/jerboa-emacs/qt/commands-config.sls
+++ b/lib/jerboa-emacs/qt/commands-config.sls
@@ -899,8 +899,8 @@
                  (loop (cdr segs) (+ pos text-len)))))))
   (define terminal-buffer-counter--cell (vector 0))
   (def (cmd-term app)
-       "Open a QTerminalWidget-backed terminal running jsh as the shell."
-       (verbose-log! "cmd-term: begin (QTerminalWidget + jsh)")
+       "Open a QTerminalSession-backed terminal running jsh as the shell.\n   The session is shared by all windows showing the buffer; each window gets\n   its own QTerminalView, so splitting the window shows the same live terminal."
+       (verbose-log! "cmd-term: begin (QTerminalSession + jsh)")
        (let* ([fr (app-state-frame app)]
               [ed (current-qt-editor app)]
               [name (begin
@@ -927,24 +927,22 @@
            (lambda ()
              (let* ([win (qt-current-window fr)]
                     [container (qt-edit-window-container win)]
-                    [term (qt-terminal-create container)]
+                    [session (qt-terminal-create container)]
                     [jsh-path (or (getenv "JSH") "/usr/local/bin/jsh")])
                (qt-terminal-set-font!
-                 term
+                 session
                  *default-font-family*
                  *default-font-size*)
-               (qt-terminal-set-colors! term 12305103 2632756)
-               (qt-stacked-widget-add-widget!
-                 container
-                 (qt-terminal-widget term))
-               (qt-stacked-widget-set-current-widget!
-                 container
-                 (qt-terminal-widget term))
-               (qt-terminal-spawn! term jsh-path)
-               (hash-put! *terminal-widget-map* buf term)
-               (hash-put! *terminal-container-map* buf container)
-               ((app-state-key-handler app) (qt-terminal-widget term))
-               (qt-terminal-focus! term)
+               (qt-terminal-set-colors! session 12305103 2632756)
+               (hash-put! *terminal-widget-map* buf session)
+               (let ([view (terminal-ensure-view!
+                             app
+                             buf
+                             session
+                             container)])
+                 (qt-stacked-widget-set-current-widget! container view)
+                 (qt-terminal-view-focus! view)
+                 (qt-terminal-spawn! session jsh-path))
                (verbose-log! "cmd-term: spawned jsh=" jsh-path)
                (echo-message!
                  (app-state-echo app)
diff --git a/lib/jerboa-emacs/qt/commands-shell.sls b/lib/jerboa-emacs/qt/commands-shell.sls
index f6d38cb..d28bec5 100644
--- a/lib/jerboa-emacs/qt/commands-shell.sls
+++ b/lib/jerboa-emacs/qt/commands-shell.sls
@@ -4,7 +4,8 @@
 
 (library (jerboa-emacs qt commands-shell)
   (export directory-exists? *terminal-widget-map*
-   *terminal-container-map* apply-font-size-to-all-editors!
+   *terminal-views-map* terminal-view-for-container
+   terminal-ensure-view! apply-font-size-to-all-editors!
    cmd-increase-font-size cmd-decrease-font-size
    cmd-reset-font-size cmd-goto-first-non-blank
    cmd-goto-last-non-blank cmd-move-to-window-top
@@ -118,8 +119,27 @@
        (and (file-exists? path) (file-directory? path)))
   (define *terminal-widget-map*--cell
     (vector (make-hash-table-eq)))
-  (define *terminal-container-map*--cell
+  (define *terminal-views-map*--cell
     (vector (make-hash-table-eq)))
+  (def (terminal-view-for-container buf container)
+       "Return the QTerminalView showing terminal BUF inside CONTAINER, or #f.\n   Containers are foreign pointers, so compare with equal? (via assoc)."
+       (let ([views (hash-get *terminal-views-map* buf)])
+         (cond
+           [(and views (assoc container views)) => cdr]
+           [else #f])))
+  (def (terminal-ensure-view! app buf session container)
+       "Get or create the QTerminalView showing SESSION inside CONTAINER. Adds it as\n   a QStackedWidget page and installs the consuming key filter exactly once.\n   Returns the view."
+       (or (terminal-view-for-container buf container)
+           (let ([view (qt-terminal-view-create session container)])
+             (qt-stacked-widget-add-widget! container view)
+             (let ([kh (app-state-key-handler app)]) (when kh (kh view)))
+             (hash-put!
+               *terminal-views-map*
+               buf
+               (cons
+                 (cons container view)
+                 (or (hash-get *terminal-views-map* buf) '())))
+             view)))
   (def (apply-font-size-to-all-editors! app)
        "Apply the current global font size to all open editors."
        (let ([fr (app-state-frame app)]
@@ -2280,11 +2300,11 @@
                        *terminal-widget-map*--cell
                        0
                        val)]))
-  (define-syntax *terminal-container-map*
+  (define-syntax *terminal-views-map*
     (identifier-syntax
-      [id (vector-ref *terminal-container-map*--cell 0)]
+      [id (vector-ref *terminal-views-map*--cell 0)]
       [(set! id val) (vector-set!
-                       *terminal-container-map*--cell
+                       *terminal-views-map*--cell
                        0
                        val)]))
   (define-syntax *auto-indent*
diff --git a/lib/jerboa-emacs/qt/commands.sls b/lib/jerboa-emacs/qt/commands.sls
index 0f4d2cc..7480e9c 100644
--- a/lib/jerboa-emacs/qt/commands.sls
+++ b/lib/jerboa-emacs/qt/commands.sls
@@ -5,7 +5,8 @@
 (library (jerboa-emacs qt commands)
   (export qt-register-all-commands! dired-open-directory!
    qt-open-image-inline! *qt-app-ptr* *terminal-widget-map*
-   *terminal-container-map* qt-kill-ring-push! *isearch-active*
+   *terminal-views-map* terminal-view-for-container
+   terminal-ensure-view! 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*
@@ -662,6 +663,15 @@
                            (when ts
                              (terminal-stop! ts)
                              (hash-remove! *terminal-state* buf)))
+                         (let ([session (hash-get
+                                          *terminal-widget-map*
+                                          buf)])
+                           (when session
+                             (with-catch
+                               (lambda (e) #f)
+                               (lambda () (qt-terminal-destroy! session)))
+                             (hash-remove! *terminal-widget-map* buf)
+                             (hash-remove! *terminal-views-map* buf)))
                          (let ([cs (hash-get *chat-state* buf)])
                            (when cs
                              (chat-stop! cs)
@@ -710,6 +720,13 @@
                  (when ts
                    (terminal-stop! ts)
                    (hash-remove! *terminal-state* buf)))
+               (let ([session (hash-get *terminal-widget-map* buf)])
+                 (when session
+                   (with-catch
+                     (lambda (e) #f)
+                     (lambda () (qt-terminal-destroy! session)))
+                   (hash-remove! *terminal-widget-map* buf)
+                   (hash-remove! *terminal-views-map* buf)))
                (let ([cs (hash-get *chat-state* buf)])
                  (when cs (chat-stop! cs) (hash-remove! *chat-state* buf)))
                (set! *buffer-recent*
diff --git a/lib/jerboa-emacs/qt/sci-shim.sls b/lib/jerboa-emacs/qt/sci-shim.sls
index 1ea5641..a3939ec 100644
--- a/lib/jerboa-emacs/qt/sci-shim.sls
+++ b/lib/jerboa-emacs/qt/sci-shim.sls
@@ -142,7 +142,8 @@
    qt-terminal-send-input! qt-terminal-is-running?
    qt-terminal-interrupt! qt-terminal-set-font!
    qt-terminal-set-colors! qt-terminal-focus!
-   qt-terminal-widget)
+   qt-terminal-widget qt-terminal-view-create
+   qt-terminal-view-destroy! qt-terminal-view-focus!)
   (import
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
@@ -669,9 +670,24 @@
        "Give keyboard focus to the terminal widget."
        ((foreign-procedure "qt_terminal_focus" (void*) void) term))
   (def (qt-terminal-widget term)
-       "Return the QWidget* pointer for the terminal (for adding to QStackedWidget)."
+       "Return a representative view (active-or-first) of the terminal SESSION as a\n   QWidget* pointer. Compatibility shim — prefer qt-terminal-view-* for views."
        ((foreign-procedure "qt_terminal_widget" (void*) void*)
          term))
+  (def (qt-terminal-view-create session parent)
+       "Create a QTerminalView of SESSION parented into PARENT (a QStackedWidget).\n   Registers the view with the session; returns the view's QWidget* pointer."
+       ((foreign-procedure "qt_terminal_view_create"
+          (void* void*)
+          void*)
+         session
+         parent))
+  (def (qt-terminal-view-destroy! view)
+       "Destroy a single terminal VIEW (detach from its stack, unregister, delete).\n   The owning session and its PTY/shell are left running."
+       ((foreign-procedure "qt_terminal_view_destroy" (void*) void)
+         view))
+  (def (qt-terminal-view-focus! view)
+       "Give keyboard focus to a specific terminal VIEW."
+       ((foreign-procedure "qt_terminal_view_focus" (void*) void)
+         view))
   (define-syntax *doc-editor-map*
     (identifier-syntax
       [id (vector-ref *doc-editor-map*--cell 0)]
diff --git a/lib/jerboa-emacs/qt/window.sls b/lib/jerboa-emacs/qt/window.sls
index c622b79..f696ce8 100644
--- a/lib/jerboa-emacs/qt/window.sls
+++ b/lib/jerboa-emacs/qt/window.sls
@@ -29,7 +29,8 @@
    qt-apply-editor-theme! split-tree-flatten
    split-tree-find-parent split-tree-find-leaf
    split-tree-collect-sub-splitters qt-window-set-app-ptr!
-   qt-window-set-pre-container-destroy-fn!)
+   qt-window-set-pre-container-destroy-fn!
+   qt-window-set-post-split-focus-fn!)
   (import
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
@@ -44,6 +45,9 @@
   (def *pre-container-destroy-fn* #f)
   (def (qt-window-set-pre-container-destroy-fn! fn)
        (set! *pre-container-destroy-fn* fn))
+  (def *post-split-focus-fn* #f)
+  (def (qt-window-set-post-split-focus-fn! fn)
+       (set! *post-split-focus-fn* fn))
   (def (qt-window-pre-container-destroy! container)
        (when *pre-container-destroy-fn*
          (*pre-container-destroy-fn* container)))
@@ -530,6 +534,8 @@
               [new-win (make-qt-edit-window new-ed container buf lna #f
                          #f)])
          (qt-scintilla-setup-editor! new-ed)
+         (qt-stacked-widget-add-widget! container new-ed)
+         (hash-put! *editor-window-map* new-ed new-win)
          (qt-buffer-attach! new-ed buf)
          (let loop ([i 0])
            (when (<= i 127)
@@ -556,7 +562,6 @@
                  SCI_STYLESETFORE
                  STYLE_LINENUMBER
                  (rgb->sci r g b)))))
-         (qt-stacked-widget-add-widget! container new-ed)
          (if insert-index
              (qt-safe-splitter-insert-widget!
                "qt-make-new-window!"
@@ -567,7 +572,6 @@
                "qt-make-new-window!"
                container-parent
                container))
-         (hash-put! *editor-window-map* new-ed new-win)
          new-win))
   (def (qt-frame-init! main-win splitter)
        "Create frame with one QScintilla editor in a QStackedWidget in a QSplitter.\n   Returns the frame struct."
@@ -577,13 +581,13 @@
               [lna (qt-line-number-area-create editor)]
               [win (make-qt-edit-window editor container buf lna #f #f)])
          (qt-scintilla-setup-editor! editor)
-         (qt-buffer-attach! editor buf)
          (qt-stacked-widget-add-widget! container editor)
+         (hash-put! *editor-window-map* editor win)
+         (qt-buffer-attach! editor buf)
          (qt-safe-splitter-add-widget!
            "qt-frame-init!"
            splitter
            container)
-         (hash-put! *editor-window-map* editor win)
          (let* ([root (make-split-leaf win)]
                 [fr (make-qt-frame splitter root (list win) 0 main-win)])
            (qt-frame-update-visual-indicators! fr)
@@ -597,16 +601,7 @@
               [cur-leaf (split-tree-find-leaf (qt-frame-root fr) cur-win)]
               [parent (split-tree-find-parent (qt-frame-root fr) cur-win)]
               [cur-buf (qt-edit-window-buffer cur-win)]
-              [new-buf (if (eq? (buffer-lexer-lang cur-buf) 'terminal)
-                           (or (find
-                                 (lambda (b)
-                                   (string=?
-                                     (buffer-name b)
-                                     buffer-scratch-name))
-                                 (map qt-edit-window-buffer
-                                      (qt-frame-windows fr)))
-                               cur-buf)
-                           cur-buf)]
+              [new-buf cur-buf]
               [main-win (qt-frame-main-win fr)]
               [saved-w (and main-win (qt-widget-width main-win))]
               [saved-h (and main-win (qt-widget-height main-win))])
@@ -759,7 +754,14 @@
                saved-w
                saved-h))
            (when result
-             (qt-safe-widget-set-focus! "qt-frame-do-split!" result))
+             (let ([override (and *post-split-focus-fn*
+                                  (*post-split-focus-fn*
+                                    (list-ref
+                                      (qt-frame-windows fr)
+                                      (qt-frame-current-idx fr))))])
+               (qt-safe-widget-set-focus!
+                 "qt-frame-do-split!"
+                 (or override result))))
            (qt-frame-update-visual-indicators! fr)
            (when main-win
              (qt-safe-widget-set-updates-enabled!
diff --git a/src/jerboa-emacs/qt/app.ss b/src/jerboa-emacs/qt/app.ss
index 2507d39..fa19506 100644
--- a/src/jerboa-emacs/qt/app.ss
+++ b/src/jerboa-emacs/qt/app.ss
@@ -826,8 +826,7 @@
       ;; When showing an image, install key handler on the scroll area and
       ;; set focus there — the Scintilla editor is hidden by QStackedWidget
       ;; so it can't receive key events.
-      (let ((image-key-installed (make-hash-table-eq))
-            (terminal-key-installed (make-hash-table-eq)))
+      (let ((image-key-installed (make-hash-table-eq)))
         (add-hook! 'post-buffer-attach-hook
           (lambda (editor buf)
             (with-catch
@@ -836,26 +835,22 @@
                   (with-output-to-string (lambda () (display-exception e)))))
               (lambda ()
                 (cond
-                  ;; QTerminalWidget buffers: switch stacked to terminal view
+                  ;; Terminal buffers: ensure THIS window has a QTerminalView of
+                  ;; the shared session and show it. Every window displaying the
+                  ;; buffer gets its own live view — Emacs split semantics. The
+                  ;; key filter is installed by terminal-ensure-view! exactly once.
                   ((hash-get *terminal-widget-map* buf)
-                   => (lambda (term)
+                   => (lambda (session)
                         (let ((win (hash-get *editor-window-map* editor)))
                           (when win
                             (let* ((container (qt-edit-window-container win))
-                                   (count (qt-stacked-widget-count container))
-                                   (tw (qt-terminal-widget term)))
-                              ;; Always install consuming key filter (idempotent via guard)
-                              (unless (hash-get terminal-key-installed tw)
-                                ((app-state-key-handler app) tw)
-                                (hash-put! terminal-key-installed tw #t))
-                              (if (> count 1)
-                                ;; Terminal widget lives in THIS container — show and focus it
-                                (begin
-                                  (qt-stacked-widget-set-current-widget! container tw)
-                                  (qt-widget-set-focus! tw))
-                                ;; Terminal widget is in another window (e.g. after C-x 2).
-                                ;; Just show the editor in this window — don't steal focus.
-                                (qt-widget-set-focus! editor)))))))
+                                   (view (terminal-ensure-view! app buf session container)))
+                              (qt-stacked-widget-set-current-widget! container view)
+                              ;; Only steal focus when this is the selected window;
+                              ;; background attaches (splits, previews) must not — the
+                              ;; post-split-focus hook focuses the split's new view.
+                              (when (eq? win (qt-current-window (app-state-frame app)))
+                                (qt-terminal-view-focus! view)))))))
                   ;; Image buffers
                   ((image-buffer? buf)
                    (qt-show-image-buffer! editor buf)
@@ -1268,16 +1263,20 @@
                   ;; C-x prefix and M-x pass through to jemacs keymap.
                   ;;
                   ;; FOCUS GUARD: only forward to the terminal if the key event came
-                  ;; FROM the QTerminalWidget itself. If the user clicked a different
-                  ;; window (so the key came from that window's QScintilla), we must
-                  ;; NOT forward to the terminal even if qt-current-buffer is still
-                  ;; a terminal buffer (Chez state may lag the Qt focus change).
+                  ;; FROM one of this terminal's views. If the user clicked a
+                  ;; different window (so the key came from that window's QScintilla),
+                  ;; we must NOT forward even if qt-current-buffer is still a terminal
+                  ;; buffer (Chez state may lag the Qt focus change). A key from ANY
+                  ;; view of the same buffer is valid — every pane feeds one session.
                   (let* ((qt-buf (qt-current-buffer (app-state-frame app)))
                          (qt-term (and qt-buf (hash-get *terminal-widget-map* qt-buf)))
                          (key-src-widget (qt-last-key-widget))
                          (key-from-terminal? (and qt-term
-                                                   (equal? key-src-widget
-                                                           (qt-terminal-widget qt-term)))))
+                                                   (let loop ((views (or (hash-get *terminal-views-map* qt-buf) '())))
+                                                     (cond
+                                                       ((null? views) #f)
+                                                       ((equal? key-src-widget (cdr (car views))) #t)
+                                                       (else (loop (cdr views))))))))
                     (if (and qt-term
                              key-from-terminal?  ;; key must come FROM this terminal widget
                              ;; Not in a prefix key state (e.g. after C-x)
@@ -1402,41 +1401,60 @@
               (lambda (editor)
                 (qt-on-key-press-consuming! editor key-handler)))
 
-        ;; Tell automation which widget to target for key events.
-        ;; When a terminal buffer is active, the QTerminalWidget is the visible
+        ;; Tell automation which widget to target for key events. When a terminal
+        ;; buffer is active, the current window's QTerminalView is the visible
         ;; focused widget; the QScintilla editor is hidden behind it in the
-        ;; QStackedWidget. Sending sendEvent to a non-current QStackedWidget
-        ;; page is unreliable — use the visible QTerminalWidget instead.
+        ;; QStackedWidget. Target this window's view; fall back to the editor.
         (automation-set-key-target-fn!
           (lambda (fr)
-            (let* ((buf (qt-current-buffer fr))
-                   (term (and buf (hash-get *terminal-widget-map* buf))))
-              (if term
-                (qt-terminal-widget term)
-                (qt-current-editor fr)))))
-
-        ;; Install pre-container-destroy hook so that when any window container
-        ;; (QStackedWidget) is about to be destroyed (by delete-other-windows,
-        ;; C-x 0, kill-terminal-buffer, etc.), any terminal living inside it
-        ;; is detached and destroyed first — preventing the double-free crash
-        ;; that occurs when Qt auto-deletes the terminal as a child.
+            (let* ((win (qt-current-window fr))
+                   (buf (qt-edit-window-buffer win))
+                   (view (and buf
+                              (hash-get *terminal-widget-map* buf)
+                              (terminal-view-for-container
+                                buf (qt-edit-window-container win)))))
+              (or view (qt-current-editor fr)))))
+
+        ;; Install pre-container-destroy hook: when any window container
+        ;; (QStackedWidget) is about to be destroyed (delete-other-windows, C-x 0,
+        ;; kill-buffer-and-window, etc.), destroy only the QTerminalView(s) living
+        ;; in THAT container — detached first to avoid the Qt parent-child
+        ;; double-free. The QTerminalSession and its shell keep running, so the
+        ;; terminal survives in the other windows (Emacs semantics: closing a
+        ;; window does not kill the process).
         (qt-window-set-pre-container-destroy-fn!
           (lambda (container)
-            (let ((bufs-to-remove '()))
+            ;; Snapshot entries first, then mutate (never edit a hash mid-iteration).
+            (let ((entries '()))
               (hash-for-each
-                (lambda (buf stored-container)
-                  (when (equal? stored-container container)
-                    (let ((term (hash-get *terminal-widget-map* buf)))
-                      (when term
-                        (with-catch (lambda (e) #f)
-                          (lambda () (qt-terminal-destroy! term)))))
-                    (set! bufs-to-remove (cons buf bufs-to-remove))))
-                *terminal-container-map*)
+                (lambda (buf views) (set! entries (cons (cons buf views) entries)))
+                *terminal-views-map*)
               (for-each
-                (lambda (buf)
-                  (hash-remove! *terminal-widget-map* buf)
-                  (hash-remove! *terminal-container-map* buf))
-                bufs-to-remove)))))
+                (lambda (e)
+                  (let* ((buf   (car e))
+                         (views (cdr e))
+                         (mine  (filter (lambda (cv) (equal? (car cv) container)) views))
+                         (rest  (filter (lambda (cv) (not (equal? (car cv) container))) views)))
+                    (unless (null? mine)
+                      (for-each
+                        (lambda (cv)
+                          (with-catch (lambda (e) #f)
+                            (lambda () (qt-terminal-view-destroy! (cdr cv)))))
+                        mine)
+                      (hash-put! *terminal-views-map* buf rest))))
+                entries)))))
+
+        ;; Install post-split-focus hook: after a split, if the new window shows
+        ;; a terminal, focus its freshly-created QTerminalView rather than the
+        ;; hidden editor page behind it. Returns #f for non-terminal buffers so
+        ;; the splitter falls back to focusing the new editor.
+        (qt-window-set-post-split-focus-fn!
+          (lambda (new-win)
+            (let* ((buf (qt-edit-window-buffer new-win))
+                   (session (and buf (hash-get *terminal-widget-map* buf))))
+              (and session
+                   (terminal-view-for-container
+                     buf (qt-edit-window-container new-win))))))
 
       ;; ================================================================
       ;; Periodic tasks — registered with schedule-periodic!, driven
@@ -1975,42 +1993,41 @@
               (cons 'test-prefix-active?
                     (lambda ()
                       (not (null? (key-state-prefix-keys (app-state-key-state app))))))
-              ;; Is the current buffer a QTerminalWidget terminal?
+              ;; Does the current window actually display a live terminal view?
               (cons 'test-terminal-running?
-                    ;; True only if the current window's QStackedWidget is actually
-                    ;; showing the QTerminalWidget page (not just the buffer being a
-                    ;; terminal buffer — after C-x 2, new window shows editor page).
+                    ;; True iff the current window has a QTerminalView of the
+                    ;; terminal buffer AND its stack is showing a page > 0 (the
+                    ;; editor is page 0). After C-x 2 each pane has its own view,
+                    ;; so this is true in every window showing the terminal.
                     (lambda ()
                       (let* ((fr  (app-state-frame app))
                              (win (qt-current-window fr))
                              (buf (qt-edit-window-buffer win))
-                             (term (and buf (hash-get *terminal-widget-map* buf))))
-                        (and term
-                             (let* ((container (qt-edit-window-container win))
-                                    (count (qt-stacked-widget-count container)))
-                               ;; Terminal widget was added as the last page (index count-1).
-                               ;; If count > 1 and current index > 0, terminal is visible.
-                               (and (> count 1)
-                                    (> (qt-stacked-widget-current-index container) 0)))))))
+                             (session (and buf (hash-get *terminal-widget-map* buf)))
+                             (container (qt-edit-window-container win)))
+                        (and session
+                             (terminal-view-for-container buf container)
+                             (> (qt-stacked-widget-current-index container) 0)
+                             #t))))
               ;; Reset editor to a clean single-window state between tests.
               ;; Clears key prefix state, collapses to one window, destroys terminals.
               (cons 'test-reset!
                     (lambda ()
                       ;; Clear any pending prefix key (e.g. C-x)
                       (set! (app-state-key-state app) (make-initial-key-state))
-                      ;; Destroy terminals BEFORE delete-other-windows: qt-terminal-destroy!
-                      ;; detaches the widget from its parent QStackedWidget, preventing the
-                      ;; double-free that occurs when delete-other-windows destroys the
-                      ;; container and Qt auto-deletes its children.
+                      ;; Destroy terminal SESSIONS before delete-other-windows.
+                      ;; qt-terminal-destroy! tears down every view (detaching each
+                      ;; from its container) and the PTY, preventing the double-free
+                      ;; when delete-other-windows later destroys the containers.
                       (let ((term-bufs (hash-keys *terminal-widget-map*)))
                         (for-each
                           (lambda (buf)
-                            (let ((term (hash-get *terminal-widget-map* buf)))
-                              (when term
+                            (let ((session (hash-get *terminal-widget-map* buf)))
+                              (when session
                                 (with-catch (lambda (e) #f)
-                                  (lambda () (qt-terminal-destroy! term)))
+                                  (lambda () (qt-terminal-destroy! session)))
                                 (hash-remove! *terminal-widget-map* buf)
-                                (hash-remove! *terminal-container-map* buf))))
+                                (hash-remove! *terminal-views-map* buf))))
                           term-bufs))
                       ;; Collapse to single window (safe now: terminals detached from containers)
                       (when (> (length (qt-frame-windows (app-state-frame app))) 1)
diff --git a/src/jerboa-emacs/qt/commands-config.ss b/src/jerboa-emacs/qt/commands-config.ss
index d9e9add..87f261b 100644
--- a/src/jerboa-emacs/qt/commands-config.ss
+++ b/src/jerboa-emacs/qt/commands-config.ss
@@ -753,11 +753,14 @@ modified so the next save uses the new encoding."
 
 (def terminal-buffer-counter 0)
 
-;; *terminal-widget-map* and *terminal-container-map* are defined in commands-shell
+;; *terminal-widget-map*, *terminal-views-map* and terminal-ensure-view! are
+;; defined in commands-shell (imported here).
 
 (def (cmd-term app)
-  "Open a QTerminalWidget-backed terminal running jsh as the shell."
-  (verbose-log! "cmd-term: begin (QTerminalWidget + jsh)")
+  "Open a QTerminalSession-backed terminal running jsh as the shell.
+   The session is shared by all windows showing the buffer; each window gets
+   its own QTerminalView, so splitting the window shows the same live terminal."
+  (verbose-log! "cmd-term: begin (QTerminalSession + jsh)")
   (let* ((fr (app-state-frame app))
          (ed (current-qt-editor app))
          (name (begin
@@ -779,17 +782,19 @@ modified so the next save uses the new encoding."
       (lambda ()
         (let* ((win (qt-current-window fr))
                (container (qt-edit-window-container win))
-               (term (qt-terminal-create container))
+               (session (qt-terminal-create container))
                (jsh-path (or (getenv "JSH") "/usr/local/bin/jsh")))
-          (qt-terminal-set-font! term *default-font-family* *default-font-size*)
-          (qt-terminal-set-colors! term #xbbc2cf #x282c34)
-          (qt-stacked-widget-add-widget! container (qt-terminal-widget term))
-          (qt-stacked-widget-set-current-widget! container (qt-terminal-widget term))
-          (qt-terminal-spawn! term jsh-path)
-          (hash-put! *terminal-widget-map* buf term)
-          (hash-put! *terminal-container-map* buf container)
-          ((app-state-key-handler app) (qt-terminal-widget term))
-          (qt-terminal-focus! term)
+          (qt-terminal-set-font! session *default-font-family* *default-font-size*)
+          (qt-terminal-set-colors! session #xbbc2cf #x282c34)
+          ;; Map the session BEFORE creating the view: the post-buffer-attach
+          ;; hook may fire re-entrantly and must find the session to attach a view.
+          (hash-put! *terminal-widget-map* buf session)
+          (let ((view (terminal-ensure-view! app buf session container)))
+            (qt-stacked-widget-set-current-widget! container view)
+            ;; Focus before spawning so the session sizes the PTY from the real
+            ;; pane before the shell prints its prompt.
+            (qt-terminal-view-focus! view)
+            (qt-terminal-spawn! session jsh-path))
           (verbose-log! "cmd-term: spawned jsh=" jsh-path)
           (echo-message! (app-state-echo app) (string-append name " started")))))))
 
@@ -1175,4 +1180,3 @@ If on blank line: insert comment and indent."
                            (if (>= i line) (+ offset (string-length new-line))
                              (loop (+ i 1) (+ offset (string-length (list-ref new-lines i)) 1))))))
                (qt-plain-text-edit-set-cursor-position! ed pos)))))))))
-
diff --git a/src/jerboa-emacs/qt/commands-shell.ss b/src/jerboa-emacs/qt/commands-shell.ss
index 8cd65ab..09383ce 100644
--- a/src/jerboa-emacs/qt/commands-shell.ss
+++ b/src/jerboa-emacs/qt/commands-shell.ss
@@ -59,15 +59,39 @@
 ;;; Batch 8: Remaining missing commands
 ;;;============================================================================
 
-;; Map buffer → QTerminalWidget pointer for the libvterm-based terminal
-;; Defined here (commands-shell) because commands-config imports commands-shell,
-;; so moving it here makes it visible to both without circular dependency.
+;; Map terminal buffer → QTerminalSession pointer (the model: PTY + libvterm).
+;; One session per terminal buffer, shared by every window showing it; each
+;; window gets its own QTerminalView. Defined here (commands-shell) because
+;; commands-config imports commands-shell, so both see it without a circular
+;; dependency. (Name kept for compatibility — values are now sessions.)
 (def *terminal-widget-map* (make-hash-table-eq))
 
-;; Map buffer → QStackedWidget container for the terminal's host window.
-;; Lets the pre-container-destroy hook identify which terminal is in a container
-;; that is about to be destroyed, so it can detach/destroy the terminal first.
-(def *terminal-container-map* (make-hash-table-eq))
+;; Map terminal buffer → list of (container . view) pairs, one entry per window
+;; currently displaying the terminal. CONTAINER is the window's QStackedWidget;
+;; VIEW is the QTerminalView page inside it. Lets the pre-container-destroy hook
+;; tear down only the view(s) in a dying container while the session lives on.
+(def *terminal-views-map* (make-hash-table-eq))
+
+(def (terminal-view-for-container buf container)
+  "Return the QTerminalView showing terminal BUF inside CONTAINER, or #f.
+   Containers are foreign pointers, so compare with equal? (via assoc)."
+  (let ((views (hash-get *terminal-views-map* buf)))
+    (cond ((and views (assoc container views)) => cdr)
+          (else #f))))
+
+(def (terminal-ensure-view! app buf session container)
+  "Get or create the QTerminalView showing SESSION inside CONTAINER. Adds it as
+   a QStackedWidget page and installs the consuming key filter exactly once.
+   Returns the view."
+  (or (terminal-view-for-container buf container)
+      (let ((view (qt-terminal-view-create session container)))
+        (qt-stacked-widget-add-widget! container view)
+        (let ((kh (app-state-key-handler app)))
+          (when kh (kh view)))
+        (hash-put! *terminal-views-map* buf
+                   (cons (cons container view)
+                         (or (hash-get *terminal-views-map* buf) '())))
+        view)))
 
 ;; --- Font size ---
 ;; Note: Font size state is now in face.ss (*default-font-size*)
diff --git a/src/jerboa-emacs/qt/commands.ss b/src/jerboa-emacs/qt/commands.ss
index 221b387..0777063 100644
--- a/src/jerboa-emacs/qt/commands.ss
+++ b/src/jerboa-emacs/qt/commands.ss
@@ -7,7 +7,9 @@
         qt-open-image-inline!
         *qt-app-ptr*
         *terminal-widget-map*
-        *terminal-container-map*
+        *terminal-views-map*
+        terminal-view-for-container
+        terminal-ensure-view!
         qt-kill-ring-push!
         *isearch-active*
         isearch-handle-key!
@@ -623,6 +625,15 @@
                 (when ts
                   (terminal-stop! ts)
                   (hash-remove! *terminal-state* buf)))
+              ;; Clean up QTerminalSession (PTY + all views) if applicable.
+              ;; Destroying the session detaches every view from its container,
+              ;; so later window teardown can't double-free them.
+              (let ((session (hash-get *terminal-widget-map* buf)))
+                (when session
+                  (with-catch (lambda (e) #f)
+                    (lambda () (qt-terminal-destroy! session)))
+                  (hash-remove! *terminal-widget-map* buf)
+                  (hash-remove! *terminal-views-map* buf)))
               ;; Clean up chat state if applicable
               (let ((cs (hash-get *chat-state* buf)))
                 (when cs
@@ -666,6 +677,12 @@
           (when ss (shell-stop! ss) (hash-remove! *shell-state* buf)))
         (let ((ts (hash-get *terminal-state* buf)))
           (when ts (terminal-stop! ts) (hash-remove! *terminal-state* buf)))
+        (let ((session (hash-get *terminal-widget-map* buf)))
+          (when session
+            (with-catch (lambda (e) #f)
+              (lambda () (qt-terminal-destroy! session)))
+            (hash-remove! *terminal-widget-map* buf)
+            (hash-remove! *terminal-views-map* buf)))
         (let ((cs (hash-get *chat-state* buf)))
           (when cs (chat-stop! cs) (hash-remove! *chat-state* buf)))
         (set! *buffer-recent*
diff --git a/src/jerboa-emacs/qt/sci-shim.ss b/src/jerboa-emacs/qt/sci-shim.ss
index dc9a883..4b3bf59 100644
--- a/src/jerboa-emacs/qt/sci-shim.ss
+++ b/src/jerboa-emacs/qt/sci-shim.ss
@@ -137,13 +137,14 @@
   QT_CURSOR_START_OF_BLOCK QT_CURSOR_END_OF_BLOCK
   QT_CURSOR_NEXT_CHAR QT_CURSOR_NEXT_WORD
   QT_CURSOR_PREVIOUS_CHAR QT_CURSOR_PREVIOUS_WORD
-  ;; QTerminalWidget (libvterm-based terminal emulator)
+  ;; QTerminalSession / QTerminalView (libvterm-based terminal emulator)
   qt-terminal-create qt-terminal-destroy! qt-terminal-spawn!
   qt-terminal-connect-fd!
   qt-terminal-send-key-event! qt-terminal-send-input!
   qt-terminal-is-running? qt-terminal-interrupt!
   qt-terminal-set-font! qt-terminal-set-colors!
-  qt-terminal-focus! qt-terminal-widget)
+  qt-terminal-focus! qt-terminal-widget
+  qt-terminal-view-create qt-terminal-view-destroy! qt-terminal-view-focus!)
 
 (import
   :jerboa-emacs/core
@@ -649,5 +650,20 @@
   ((foreign-procedure "qt_terminal_focus" (void*) void) term))
 
 (def (qt-terminal-widget term)
-  "Return the QWidget* pointer for the terminal (for adding to QStackedWidget)."
+  "Return a representative view (active-or-first) of the terminal SESSION as a
+   QWidget* pointer. Compatibility shim — prefer qt-terminal-view-* for views."
   ((foreign-procedure "qt_terminal_widget" (void*) void*) term))
+
+(def (qt-terminal-view-create session parent)
+  "Create a QTerminalView of SESSION parented into PARENT (a QStackedWidget).
+   Registers the view with the session; returns the view's QWidget* pointer."
+  ((foreign-procedure "qt_terminal_view_create" (void* void*) void*) session parent))
+
+(def (qt-terminal-view-destroy! view)
+  "Destroy a single terminal VIEW (detach from its stack, unregister, delete).
+   The owning session and its PTY/shell are left running."
+  ((foreign-procedure "qt_terminal_view_destroy" (void*) void) view))
+
+(def (qt-terminal-view-focus! view)
+  "Give keyboard focus to a specific terminal VIEW."
+  ((foreign-procedure "qt_terminal_view_focus" (void*) void) view))
diff --git a/src/jerboa-emacs/qt/window.ss b/src/jerboa-emacs/qt/window.ss
index c61ee7e..3310e9d 100644
--- a/src/jerboa-emacs/qt/window.ss
+++ b/src/jerboa-emacs/qt/window.ss
@@ -42,7 +42,9 @@
         ;; App pointer for process-events during splits
         qt-window-set-app-ptr!
         ;; Hook called before any window container is destroyed
-        qt-window-set-pre-container-destroy-fn!)
+        qt-window-set-pre-container-destroy-fn!
+        ;; Hook to override focus target after a split (e.g. terminal views)
+        qt-window-set-post-split-focus-fn!)
 
 (import :std/sugar
         :jerboa-scintilla/constants
@@ -68,6 +70,15 @@
 (def (qt-window-set-pre-container-destroy-fn! fn)
   (set! *pre-container-destroy-fn* fn))
 
+;; Called with (new-window) after a split to decide which widget gets focus.
+;; Returns a widget to focus, or #f to fall back to the new editor. Registered
+;; by the terminal code so a split that displays a terminal focuses the new
+;; QTerminalView, not the hidden editor page behind it.
+(def *post-split-focus-fn* #f)
+
+(def (qt-window-set-post-split-focus-fn! fn)
+  (set! *post-split-focus-fn* fn))
+
 (def (qt-window-pre-container-destroy! container)
   (when *pre-container-destroy-fn*
     (*pre-container-destroy-fn* container)))
@@ -522,6 +533,12 @@
          (lna (qt-line-number-area-create new-ed))
          (new-win (make-qt-edit-window new-ed container buf lna #f #f)))
     (qt-scintilla-setup-editor! new-ed)
+    ;; Add the editor as stacked page 0 and register the window BEFORE attaching
+    ;; the buffer. qt-buffer-attach! runs the post-buffer-attach hook, which for a
+    ;; terminal buffer adds a QTerminalView as a later page — it needs the editor
+    ;; already present as page 0 and the window resolvable via *editor-window-map*.
+    (qt-stacked-widget-add-widget! container new-ed)
+    (hash-put! *editor-window-map* new-ed new-win)
     (qt-buffer-attach! new-ed buf)
     ;; Force font family+size on all styles (0-127).
     ;; QsciLexer's setLexer() overrides style fonts, so we must re-apply
@@ -540,13 +557,11 @@
       (when (and ln-face (face-fg ln-face))
         (let-values (((r g b) (parse-hex-color (face-fg ln-face))))
           (sci-send new-ed SCI_STYLESETFORE STYLE_LINENUMBER (rgb->sci r g b)))))
-    (qt-stacked-widget-add-widget! container new-ed)
     (if insert-index
       (qt-safe-splitter-insert-widget! "qt-make-new-window!"
                                        container-parent insert-index container)
       (qt-safe-splitter-add-widget! "qt-make-new-window!"
                                     container-parent container))
-    (hash-put! *editor-window-map* new-ed new-win)
     new-win))
 
 ;;;============================================================================
@@ -562,10 +577,12 @@
          (lna (qt-line-number-area-create editor))
          (win (make-qt-edit-window editor container buf lna #f #f)))
     (qt-scintilla-setup-editor! editor)
-    (qt-buffer-attach! editor buf)
+    ;; Register the window and add the editor as page 0 before attaching the
+    ;; buffer (keeps the post-buffer-attach hook's window/page lookups valid).
     (qt-stacked-widget-add-widget! container editor)
-    (qt-safe-splitter-add-widget! "qt-frame-init!" splitter container)
     (hash-put! *editor-window-map* editor win)
+    (qt-buffer-attach! editor buf)
+    (qt-safe-splitter-add-widget! "qt-frame-init!" splitter container)
     (let* ((root (make-split-leaf win))
            (fr (make-qt-frame splitter root (list win) 0 main-win)))
       ;; Set initial visual indicator for active window
@@ -591,13 +608,10 @@
          (cur-leaf  (split-tree-find-leaf (qt-frame-root fr) cur-win))
          (parent    (split-tree-find-parent (qt-frame-root fr) cur-win))
          (cur-buf   (qt-edit-window-buffer cur-win))
-         ;; Terminal buffers use a QTerminalWidget that can only live in one pane.
-         ;; Use scratch buffer for the new pane instead of the terminal buffer.
-         (new-buf   (if (eq? (buffer-lexer-lang cur-buf) 'terminal)
-                      (or (find (lambda (b) (string=? (buffer-name b) buffer-scratch-name))
-                                (map qt-edit-window-buffer (qt-frame-windows fr)))
-                          cur-buf)
-                      cur-buf))
+         ;; The new pane shows the SAME buffer as the current one — including
+         ;; terminal buffers, which now share one QTerminalSession across
+         ;; per-window QTerminalViews, so the split shows the same live terminal.
+         (new-buf   cur-buf)
          ;; Save main window geometry — adding widgets to a QSplitter can cause
          ;; Qt to resize the QMainWindow via sizeHint propagation.
          (main-win  (qt-frame-main-win fr))
@@ -713,8 +727,15 @@
       ;; Restore main window size — prevent Qt from growing the window
       (when (and main-win saved-w saved-h)