Fix uneven split sizing: qt-splitter-set-sizes! was silently failing
ober
1d84e516b1025d5f7eb09dbcff2fb5ccfdcfaa51
--- 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." --- 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))) ;;;============================================================================ --- 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 --- 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)) --- 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();