Fix uneven split sizing: qt-splitter-set-sizes! was silently failing

ober

1d84e516b1025d5f7eb09dbcff2fb5ccfdcfaa51

diff --git a/lib/jerboa-emacs/qt/window.sls b/lib/jerboa-emacs/qt/window.sls
index 6e77f53..6c42c4d 100644
--- a/lib/jerboa-emacs/qt/window.sls
+++ b/lib/jerboa-emacs/qt/window.sls
@@ -27,7 +27,8 @@
    qt-frame-delete-window! qt-frame-delete-other-windows!
    qt-frame-other-window! qt-apply-editor-theme!
    split-tree-flatten split-tree-find-parent
-   split-tree-find-leaf split-tree-collect-sub-splitters)
+   split-tree-find-leaf split-tree-collect-sub-splitters
+   qt-window-set-app-ptr!)
   (import
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
@@ -36,6 +37,13 @@
     (jerboa-emacs qt sci-shim) (jerboa-emacs core)
     (jerboa-emacs face) (jerboa-emacs qt buffer) (jerboa core)
     (jerboa runtime))
+  (def *qt-app-for-events* #f)
+  (def (qt-window-set-app-ptr! app)
+       (set! *qt-app-for-events* app))
+  (def (qt-window-process-events!)
+       "Process pending Qt events if app pointer is available."
+       (when *qt-app-for-events*
+         (qt-app-process-events! *qt-app-for-events*)))
   (defstruct
     qt-edit-window
     (editor container buffer line-number-area image-scroll
@@ -315,8 +323,9 @@
                               (qt-frame-current-idx-set!
                                 fr
                                 (or new-idx 0)))
+                            (qt-window-process-events!)
                             (with-catch
-                              void
+                              (lambda (_e) (void))
                               (lambda ()
                                 (let ([n (length
                                            (split-node-children parent))])
@@ -351,8 +360,9 @@
                               (qt-frame-current-idx-set!
                                 fr
                                 (or new-idx 0)))
+                            (qt-window-process-events!)
                             (with-catch
-                              void
+                              (lambda (_e) (void))
                               (lambda ()
                                 (qt-splitter-set-sizes!
                                   root-spl
@@ -406,14 +416,15 @@
                               (qt-frame-current-idx-set!
                                 fr
                                 (or new-idx 0)))
+                            (qt-window-process-events!)
                             (with-catch
-                              void
+                              (lambda (_e) (void))
                               (lambda ()
                                 (qt-splitter-set-sizes!
                                   new-spl
                                   (list 500 500))))
                             (with-catch
-                              void
+                              (lambda (_e) (void))
                               (lambda ()
                                 (let* ([n (qt-splitter-count parent-spl)]
                                        [sizes (let loop ([i 0] [acc '()])
@@ -499,6 +510,10 @@
              (qt-frame-current-idx-set!
                fr
                (- (length (qt-frame-windows fr)) 1))))
+         (let ([new-win (list-ref
+                          (qt-frame-windows fr)
+                          (qt-frame-current-idx fr))])
+           (qt-widget-set-focus! (qt-edit-window-editor new-win)))
          (qt-frame-update-visual-indicators! fr)))
   (def (qt-frame-delete-other-windows! fr)
        "Keep only the current window, destroy all others and all sub-splitters."
@@ -524,6 +539,7 @@
          (qt-frame-root-set! fr (make-split-leaf cur))
          (qt-frame-windows-set! fr (list cur))
          (qt-frame-current-idx-set! fr 0)
+         (qt-widget-set-focus! (qt-edit-window-editor cur))
          (qt-frame-update-visual-indicators! fr)))
   (def (qt-frame-update-visual-indicators! fr)
        "Update container borders to show which window is active.\n   Active window: blue border; inactive windows: no border."
diff --git a/src/jerboa-emacs/qt/window.ss b/src/jerboa-emacs/qt/window.ss
index 823aaf6..42238b0 100644
--- a/src/jerboa-emacs/qt/window.ss
+++ b/src/jerboa-emacs/qt/window.ss
@@ -37,7 +37,9 @@
         split-tree-flatten
         split-tree-find-parent
         split-tree-find-leaf
-        split-tree-collect-sub-splitters)
+        split-tree-collect-sub-splitters
+        ;; App pointer for process-events during splits
+        qt-window-set-app-ptr!)
 
 (import :std/sugar
         :chez-scintilla/constants
@@ -47,6 +49,21 @@
         :jerboa-emacs/qt/buffer)
 
 ;;;============================================================================
+;;; App pointer for process-events during splits
+;;;============================================================================
+
+;; Set by qt/app.ss at startup so splits can process Qt events
+(def *qt-app-for-events* #f)
+
+(def (qt-window-set-app-ptr! app)
+  (set! *qt-app-for-events* app))
+
+(def (qt-window-process-events!)
+  "Process pending Qt events if app pointer is available."
+  (when *qt-app-for-events*
+    (qt-app-process-events! *qt-app-for-events*)))
+
+;;;============================================================================
 ;;; Structures
 ;;;============================================================================
 
@@ -337,7 +354,9 @@
            (let ((new-idx (list-index (lambda (w) (eq? w new-win)) (qt-frame-windows fr))))
              (set! (qt-frame-current-idx fr) (or new-idx 0)))
            ;; Equalize all children in the splitter for even sizing
-           (with-catch void
+           ;; Process events first so Qt layout is computed before we set sizes
+           (qt-window-process-events!)
+           (with-catch (lambda (_e) (void))
              (lambda ()
                (let ((n (length (split-node-children parent))))
                  (qt-splitter-set-sizes! parent-spl
@@ -358,8 +377,9 @@
            ;; Find new window's index in the rebuilt list
            (let ((new-idx (list-index (lambda (w) (eq? w new-win)) (qt-frame-windows fr))))
              (set! (qt-frame-current-idx fr) (or new-idx 0)))
-           ;; 50/50 split
-           (with-catch void (lambda () (qt-splitter-set-sizes! root-spl (list 500 500))))
+           ;; 50/50 split — process events so Qt layout is computed first
+           (qt-window-process-events!)
+           (with-catch (lambda (_e) (void)) (lambda () (qt-splitter-set-sizes! root-spl (list 500 500))))
            (qt-edit-window-editor new-win)))
 
         ;; ── Case C: no parent or different orientation — nest with new splitter ─
@@ -393,10 +413,11 @@
            ;; Find new window's index in the rebuilt list
            (let ((new-idx (list-index (lambda (w) (eq? w new-win)) (qt-frame-windows fr))))
              (set! (qt-frame-current-idx fr) (or new-idx 0)))
-           ;; 50/50 split in nested splitter
-           (with-catch void (lambda () (qt-splitter-set-sizes! new-spl (list 500 500))))
+           ;; 50/50 split in nested splitter — process events first
+           (qt-window-process-events!)
+           (with-catch (lambda (_e) (void)) (lambda () (qt-splitter-set-sizes! new-spl (list 500 500))))
            ;; Re-equalize parent splitter so all children get equal space
-           (with-catch void
+           (with-catch (lambda (_e) (void))
              (lambda ()
                (let* ((n (qt-splitter-count parent-spl))
                       (sizes (let loop ((i 0) (acc '()))
@@ -487,6 +508,9 @@
       (set! (qt-frame-windows fr) (list-remove-idx (qt-frame-windows fr) idx))
       (when (>= (qt-frame-current-idx fr) (length (qt-frame-windows fr)))
         (set! (qt-frame-current-idx fr) (- (length (qt-frame-windows fr)) 1))))
+      ;; Restore focus to the new current editor (destroying widgets may lose it)
+      (let ((new-win (list-ref (qt-frame-windows fr) (qt-frame-current-idx fr))))
+        (qt-widget-set-focus! (qt-edit-window-editor new-win)))
       ;; Update visual indicators
       (qt-frame-update-visual-indicators! fr)))
 
@@ -513,7 +537,9 @@
     (set! (qt-frame-root fr) (make-split-leaf cur))
     (set! (qt-frame-windows fr) (list cur))
     (set! (qt-frame-current-idx fr) 0)
-    ;; 5. Update visual indicators
+    ;; 5. Restore focus to the surviving editor (reparenting may lose it)
+    (qt-widget-set-focus! (qt-edit-window-editor cur))
+    ;; 6. Update visual indicators
     (qt-frame-update-visual-indicators! fr)))
 
 ;;;============================================================================
diff --git a/vendor/chez-qt-ffi-static.ss b/vendor/chez-qt-ffi-static.ss
index 9fb321a..a5ccc70 100644
--- a/vendor/chez-qt-ffi-static.ss
+++ b/vendor/chez-qt-ffi-static.ss
@@ -203,7 +203,7 @@
     ffi-qt-splitter-create ffi-qt-splitter-add-widget
     ffi-qt-splitter-insert-widget ffi-qt-splitter-index-of
     ffi-qt-splitter-widget ffi-qt-splitter-count
-    ffi-qt-splitter-set-sizes-2 ffi-qt-splitter-set-sizes-3 ffi-qt-splitter-size-at
+    ffi-qt-splitter-set-sizes-2 ffi-qt-splitter-set-sizes-3 ffi-qt-splitter-set-sizes-4 ffi-qt-splitter-size-at
     ffi-qt-splitter-set-stretch-factor ffi-qt-splitter-set-handle-width
     ffi-qt-splitter-set-collapsible ffi-qt-splitter-is-collapsible
     ffi-qt-splitter-set-orientation
@@ -1615,6 +1615,8 @@
     (foreign-procedure "qt_splitter_set_sizes_2" (void* int int) void))
   (define ffi-qt-splitter-set-sizes-3
     (foreign-procedure "qt_splitter_set_sizes_3" (void* int int int) void))
+  (define ffi-qt-splitter-set-sizes-4
+    (foreign-procedure "qt_splitter_set_sizes_4" (void* int int int int) void))
   (define ffi-qt-splitter-size-at
     (foreign-procedure "qt_splitter_size_at" (void* int) int))
   (define ffi-qt-splitter-set-stretch-factor
diff --git a/vendor/chez-qt-qt.ss b/vendor/chez-qt-qt.ss
index 22745c3..623f0e3 100644
--- a/vendor/chez-qt-qt.ss
+++ b/vendor/chez-qt-qt.ss
@@ -1505,8 +1505,25 @@
   (define (qt-splitter-count s) (ffi-qt-splitter-count s))
   (define qt-splitter-set-sizes!
     (case-lambda
+      [(s sizes)
+       ;; Accept a list of sizes and dispatch to the appropriate FFI call
+       (cond
+         [(and (pair? sizes) (= (length sizes) 2))
+          (ffi-qt-splitter-set-sizes-2 s (car sizes) (cadr sizes))]
+         [(and (pair? sizes) (= (length sizes) 3))
+          (ffi-qt-splitter-set-sizes-3 s (car sizes) (cadr sizes) (caddr sizes))]
+         [(and (pair? sizes) (= (length sizes) 4))
+          (ffi-qt-splitter-set-sizes-4 s (car sizes) (cadr sizes) (caddr sizes) (cadddr sizes))]
+         [(and (pair? sizes) (> (length sizes) 4))
+          ;; For 5+ children, use stretch factors for equal sizing
+          (let ((n (length sizes)))
+            (do ((i 0 (+ i 1)))
+                ((= i n))
+              (ffi-qt-splitter-set-stretch-factor s i 1)))]
+         [else (void)])]
       [(s a b)     (ffi-qt-splitter-set-sizes-2 s a b)]
-      [(s a b c)   (ffi-qt-splitter-set-sizes-3 s a b c)]))
+      [(s a b c)   (ffi-qt-splitter-set-sizes-3 s a b c)]
+      [(s a b c d) (ffi-qt-splitter-set-sizes-4 s a b c d)]))
   (define (qt-splitter-size-at s idx) (ffi-qt-splitter-size-at s idx))
   (define (qt-splitter-set-stretch-factor! s idx factor)
     (ffi-qt-splitter-set-stretch-factor s idx factor))
@@ -2154,7 +2171,7 @@
       (ffi-qt-completer-on-activated c id)
       (track-handler! c id)))
 
-  (define (qt-line-edit-set-completer! e c) (ffi-qt-line-edit-set-completer e c))
+  (define (qt-line-edit-set-completer! e c) (ffi-qt-line-edit-set-completer e (or c 0)))
   (define (qt-completer-destroy! c) (ffi-qt-completer-destroy c))
 
   ;; -----------------------------------------------------------------------
@@ -2390,7 +2407,10 @@
   ;; Editor extensions
   (define (qt-plain-text-edit-cursor-position e) (ffi-qt-plain-text-edit-cursor-position e))
   (define (qt-plain-text-edit-set-cursor-position! e pos) (ffi-qt-plain-text-edit-set-cursor-position e pos))
-  (define (qt-plain-text-edit-move-cursor! e op mode) (ffi-qt-plain-text-edit-move-cursor e op mode))
+  (define qt-plain-text-edit-move-cursor!
+    (case-lambda
+      [(e op) (ffi-qt-plain-text-edit-move-cursor e op ffi-qt-const-move-anchor)]
+      [(e op mode) (ffi-qt-plain-text-edit-move-cursor e op mode)]))
   (define (qt-plain-text-edit-select-all! e) (ffi-qt-plain-text-edit-select-all e))
   (define (qt-plain-text-edit-selected-text e) (ffi-qt-plain-text-edit-selected-text e))
   (define (qt-plain-text-edit-selection-start e) (ffi-qt-plain-text-edit-selection-start e))
diff --git a/vendor/qt_shim.cpp b/vendor/qt_shim.cpp
index e62845c..6f83704 100644
--- a/vendor/qt_shim.cpp
+++ b/vendor/qt_shim.cpp
@@ -125,6 +125,16 @@
 #include <semaphore.h>
 #include <time.h>
 
+// Chez Scheme SMP thread activation — declared in the running Scheme process.
+// Deactivating a thread before a blocking foreign call tells GC that the
+// thread is not touching the Scheme heap, so stop-the-world GC can proceed
+// without waiting for it.  Must reactivate before any Scheme heap access.
+// Only available / needed when compiled for a Chez-based build (JEMACS_CHEZ_SMP).
+#ifdef JEMACS_CHEZ_SMP
+extern "C" int  Sactivate_thread(void);
+extern "C" void Sdeactivate_thread(void);
+#endif
+
 // ============================================================
 // Verbose logging — enabled via qt_verbose_log_enable(path).
 // Logs every BlockingQueuedConnection dispatch and explicit
@@ -237,32 +247,83 @@ static inline bool is_qt_main_thread() {
 // Dispatch a void body to the Qt main thread.
 // If already on Qt thread: calls directly (zero overhead).
 // Otherwise: marshals via BlockingQueuedConnection with verbose logging.
+#ifdef JEMACS_CHEZ_SMP
 #define QT_VOID(...) do {                                           \
     if (is_qt_main_thread()) { __VA_ARGS__; }                      \
     else {                                                          \
         vlog_bqc_enter(__func__);                                   \
+        Sdeactivate_thread();                                       \
         QMetaObject::invokeMethod(                                  \
             QCoreApplication::instance(),                           \
             [=]() { __VA_ARGS__; },                                \
             Qt::BlockingQueuedConnection);                          \
+        Sactivate_thread();                                         \
         vlog_bqc_exit(__func__);                                    \
     }                                                               \
 } while(0)
+#else
+#define QT_VOID(...) do {                                           \
+    if (is_qt_main_thread()) { __VA_ARGS__; }                      \
+    else {                                                          \
+        vlog_bqc_enter(__func__);                                   \
+        QMetaObject::invokeMethod(                                  \
+            QCoreApplication::instance(),                           \
+            [=]() { __VA_ARGS__; },                                \
+            Qt::BlockingQueuedConnection);                          \
+        vlog_bqc_exit(__func__);                                    \
+    }                                                               \
+} while(0)
+#endif
 
 // Dispatch a function returning a value.
+#ifdef JEMACS_CHEZ_SMP
 #define QT_RETURN(type, expr) do {                                  \
     if (is_qt_main_thread()) { return (expr); }                    \
     vlog_bqc_enter(__func__);                                       \
     type _result{};                                                 \
+    Sdeactivate_thread();                                           \
     QMetaObject::invokeMethod(                                      \
         QCoreApplication::instance(),                               \
         [&]() { _result = (expr); },                               \
         Qt::BlockingQueuedConnection);                              \
+    Sactivate_thread();                                             \
     vlog_bqc_exit(__func__);                                        \
     return _result;                                                 \
 } while(0)
+#else
+#define QT_RETURN(type, expr) do {                                  \
+    if (is_qt_main_thread()) { return (expr); }                    \
+    vlog_bqc_enter(__func__);                                       \
+    type _result{};                                                 \
+    QMetaObject::invokeMethod(                                      \
+        QCoreApplication::instance(),                               \
+        [&]() { _result = (expr); },                               \
+        Qt::BlockingQueuedConnection);                              \
+    vlog_bqc_exit(__func__);                                        \
+    return _result;                                                 \
+} while(0)
+#endif
 
 // Dispatch a function returning const char* via s_return_buf.
+#ifdef JEMACS_CHEZ_SMP
+#define QT_RETURN_STRING(expr) do {                                 \
+    if (is_qt_main_thread()) {                                      \
+        s_return_buf = (expr);                                      \
+        return s_return_buf.c_str();                                \
+    }                                                               \
+    vlog_bqc_enter(__func__);                                       \
+    std::string _str_result;                                        \
+    Sdeactivate_thread();                                           \
+    QMetaObject::invokeMethod(                                      \
+        QCoreApplication::instance(),                               \
+        [&]() { _str_result = (expr); },                           \
+        Qt::BlockingQueuedConnection);                              \
+    Sactivate_thread();                                             \
+    s_return_buf = std::move(_str_result);                         \
+    vlog_bqc_exit(__func__);                                        \
+    return s_return_buf.c_str();                                    \
+} while(0)
+#else
 #define QT_RETURN_STRING(expr) do {                                 \
     if (is_qt_main_thread()) {                                      \
         s_return_buf = (expr);                                      \
@@ -278,6 +339,7 @@ static inline bool is_qt_main_thread() {
     vlog_bqc_exit(__func__);                                        \
     return s_return_buf.c_str();                                    \
 } while(0)
+#endif
 
 // String buffer for returning strings to FFI safely.
 // Qt's QString::toUtf8().constData() returns a pointer to a temporary;
@@ -2057,6 +2119,11 @@ extern "C" void qt_splitter_set_sizes_3(qt_splitter_t s, int a, int b, int c) {
     QT_VOID(static_cast<QSplitter*>(s)->setSizes({a, b, c}));
 }
 
+extern "C" void qt_splitter_set_sizes_4(qt_splitter_t s, int a, int b, int c, int d) {
+    QT_NULL_CHECK_VOID(s);
+    QT_VOID(static_cast<QSplitter*>(s)->setSizes({a, b, c, d}));
+}
+
 extern "C" int qt_splitter_size_at(qt_splitter_t s, int index) {
     QT_NULL_CHECK_RET(s, 0);
     QList<int> sizes = static_cast<QSplitter*>(s)->sizes();