Fix vterm copy mode and test build issues
ober
00f32ab4586b1c7e2dd6dafa048ac2c1d69cccd3
--- a/Makefile +++ b/Makefile @@ -122,14 +122,16 @@ endif NATIVE_LIB := $(JERBOA)/lib/libjerboa_native.$(SHLIB_EXT) SSL_SHIM := $(JERBOA_SSL_ROOT)/jerboa_ssl_shim.$(SHLIB_EXT) SCINTILLA_SHIM := ./jerboa_scintilla_shim.$(SHLIB_EXT) -PRELOAD_ENV := $(PRELOAD_VAR)=./libqt_shim.$(SHLIB_EXT):./jerboa_qt_shim.$(SHLIB_EXT):$(NATIVE_LIB):./libjsh-ffi.$(SHLIB_EXT):./libjsh_coreutils_stub.$(SHLIB_EXT):$(SSL_SHIM) -CORE_PRELOAD_ENV := $(PRELOAD_VAR)=$(NATIVE_LIB):$(SCINTILLA_SHIM):./libjsh-ffi.$(SHLIB_EXT):./libjsh_coreutils_stub.$(SHLIB_EXT):$(SSL_SHIM) +TLS_STUB := ./jemacs_tls_stubs.$(SHLIB_EXT) +PRELOAD_ENV := $(PRELOAD_VAR)=./libqt_shim.$(SHLIB_EXT):./jerboa_qt_shim.$(SHLIB_EXT):$(NATIVE_LIB):$(TLS_STUB):./libjsh-ffi.$(SHLIB_EXT):./libjsh_coreutils_stub.$(SHLIB_EXT):$(SSL_SHIM) +CORE_PRELOAD_ENV := $(PRELOAD_VAR)=$(NATIVE_LIB):$(TLS_STUB):$(SCINTILLA_SHIM):./libjsh-ffi.$(SHLIB_EXT):./libjsh_coreutils_stub.$(SHLIB_EXT):$(SSL_SHIM) # ----------------------------------------------------------------------------- export JERBOA_SCINTILLA_LIB := $(CURDIR) export JERBOA_PCRE2_LIB := $(JERBOA_PCRE2_ROOT) export JSH_FFI_LIB := $(JSH_ROOT) export JERBOA_SSL_DIR := $(JERBOA_SSL_ROOT) +export JEMACS_TLS_STUB_DIR := $(CURDIR) export JERBOA_QT_LIB := . export JERBOA_QT_SHIM_DIR := . ifeq ($(UNAME_S),Darwin) @@ -492,6 +494,9 @@ libjsh-ffi.$(SHLIB_EXT): $(JSH_ROOT)/ffi-shim.c libjsh_coreutils_stub.$(SHLIB_EXT): support/jsh_coreutils_stubs.c gcc $(SHLIB_FLAGS) -O2 -o libjsh_coreutils_stub.$(SHLIB_EXT) support/jsh_coreutils_stubs.c -Wall +jemacs_tls_stubs.$(SHLIB_EXT): support/jemacs_tls_stubs.c + gcc $(SHLIB_FLAGS) -O2 -o jemacs_tls_stubs.$(SHLIB_EXT) support/jemacs_tls_stubs.c -Wall + $(NATIVE_LIB): $(MAKE) -C $(JERBOA) native @@ -499,17 +504,17 @@ $(SSL_SHIM): $(JERBOA_SSL_ROOT)/jerboa_ssl_shim.c $(MAKE) -C $(JERBOA_SSL_ROOT) JERBOA_HOME=$(JERBOA) SCHEME=$(SCHEME) JERBUILD=$(VENDORED_JERBUILD) # macOS native binary — compile all modules + link jemacs-qt binary -macos: build libqt_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) repl_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) +macos: build libqt_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) repl_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(MAKE) -C $(JERBOA_PCRE2_ROOT) SCHEME=$(SCHEME) JSH_DIR=$(JSH) $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script build-binary-qt.ss ./jemacs-qt --version -run-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) +run-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script qt-main.ss # Headless Qt with automation REPL (for Claude). Uses xvfb-run for # virtual display on Linux (static binary needs xcb). Auto-assigned REPL port. -run-qt-test: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) +run-qt-test: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) @rm -f $(HOME)/.jerboa-repl-port @LOG="$${TMPDIR:-/tmp}/jemacs-qt-test.log"; \ rm -f "$$LOG"; \ @@ -596,11 +601,11 @@ test-command-registry: test-tier3: $(SCHEME) $(LIBDIRS) --script tests/test-tier3.ss -test-tier4: - $(SCHEME) $(LIBDIRS) --program tests/test-tier4.ss +test-tier4: jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) + $(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-tier4.ss -test-tier5: - $(SCHEME) $(LIBDIRS) --program tests/test-tier5.ss +test-tier5: jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) + $(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-tier5.ss test-org: test-org-parse test-org-clock test-org-table test-org-agenda \ test-org-babel test-org-capture test-org-export test-org-list \ @@ -664,13 +669,13 @@ test-org-src: test-org-tempo: $(SCHEME) $(LIBDIRS) --script tests/test-org-tempo.ss -test-vtscreen: - $(SCHEME) $(LIBDIRS) --script tests/test-vtscreen.ss +test-vtscreen: jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) + $(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script tests/test-vtscreen.ss test-debug-repl: $(SCHEME) $(LIBDIRS) --program tests/test-debug-repl.ss -test-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +test-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) QT_QPA_PLATFORM=offscreen $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script tests/test-qt.ss @if [ -f tests/test-qt-part2.ss ]; then \ QT_QPA_PLATFORM=offscreen $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script tests/test-qt-part2.ss; \ @@ -682,17 +687,17 @@ test-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB test-qt-e2e: bash tests/test-qt-functional.sh ./jemacs-qt -test-emacs: - $(SCHEME) $(LIBDIRS) --program tests/test-emacs.ss +test-emacs: jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) + $(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-emacs.ss -test-functional: build jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +test-functional: build jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-functional.ss test-pty: $(SCHEME) $(LIBDIRS) --script tests/test-pty.ss -test-term-hang: - $(SCHEME) $(LIBDIRS) --program tests/test-term-hang.ss +test-term-hang: jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) + $(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-term-hang.ss binary-qt: build $(SCHEME) $(LIBDIRS) --script build-binary-qt.ss @@ -1688,38 +1693,38 @@ stress-test: $(SCHEME) $(LIBDIRS) --script tests/stress-test.ss --port $(STRESS_PORT) --profile $(STRESS_PROFILE) # Focused all-in-one stress profiles. -stress-window: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +stress-window: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(STRESS_ENV) scripts/run-stress-profile.sh window interpreted stress-window-static: $(STRESS_ENV) scripts/run-stress-profile.sh window static -stress-buffer: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +stress-buffer: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(STRESS_ENV) scripts/run-stress-profile.sh buffer interpreted stress-buffer-static: $(STRESS_ENV) scripts/run-stress-profile.sh buffer static -stress-edit: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +stress-edit: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(STRESS_ENV) scripts/run-stress-profile.sh edit interpreted stress-edit-static: $(STRESS_ENV) scripts/run-stress-profile.sh edit static -stress-file: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +stress-file: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(STRESS_ENV) scripts/run-stress-profile.sh file interpreted stress-file-static: $(STRESS_ENV) scripts/run-stress-profile.sh file static -stress-chaos: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +stress-chaos: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(STRESS_ENV) scripts/run-stress-profile.sh chaos interpreted stress-chaos-static: $(STRESS_ENV) scripts/run-stress-profile.sh chaos static # Backward-compatible aliases for the historical all-in-one burn-in. -stress-burn: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) +stress-burn: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) $(STRESS_ENV) scripts/run-stress-profile.sh chaos interpreted # Backward-compatible alias for static gdb burn-in. @@ -1746,7 +1751,7 @@ scenario-test: $(if $(SCENARIO_NO_SHOTS),--no-screenshots) # All-in-one: launch interpreted jemacs-qt + run scenario driver -scenario-burn: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) +scenario-burn: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) @echo "=== Starting jemacs-qt scenario burn-in ===" @rm -f $(HOME)/.jerboa-repl-port scenario-run.log scenario-failures.log @rm -rf scenario-screenshots --- a/lib/jerboa-emacs/qt/app.sls +++ b/lib/jerboa-emacs/qt/app.sls @@ -1050,6 +1050,19 @@ echo-label)] [*snake-active* (snake-handle-key! app code mods text)] + [(and (or (= code QT_KEY_RETURN) + (= code QT_KEY_ENTER)) + (zero? + (bitwise-and mods QT_MOD_CTRL)) + (zero? (bitwise-and mods QT_MOD_ALT)) + (terminal-copy-mode-active? + (qt-current-buffer + (app-state-frame app)))) + (execute-command! app 'vterm-copy-done) + (qt-refresh-after-key! + app + echo-label + "vterm-copy-done")] [else (letrec ([terminal-pty-intercept? (lambda (code mods @@ -2016,13 +2029,17 @@ (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))]) + (lambda (win) + (let* ([buf (qt-edit-window-buffer win)] + [container (qt-edit-window-container win)] + [session (and buf (hash-get *terminal-widget-map* buf))] + [view (and session + (> (qt-stacked-widget-current-index container) + 0) + (terminal-view-for-container buf container))]) (and session - (terminal-view-for-container - buf - (qt-edit-window-container new-win)))))) + view + (begin (qt-terminal-view-focus! view) view))))) (qt-startup-trace "key handler setup done") (qt-startup-trace "periodic repl-poll begin") (schedule-periodic! --- a/lib/jerboa-emacs/qt/commands-config.sls +++ b/lib/jerboa-emacs/qt/commands-config.sls @@ -21,7 +21,8 @@ *SCI_SETCODEPAGE* *SC_CP_UTF8* qt-insert-prompt! 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* vterm-native-session + cmd-multi-vterm *terminal-copy-mode* + terminal-copy-mode-active? vterm-native-session 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) @@ -1174,6 +1175,9 @@ (cmd-term app)) (define *terminal-copy-mode*--cell (vector (make-hash-table))) + (def (terminal-copy-mode-active? buf) + "Return true when BUF is currently showing a frozen vterm copy buffer." + (and buf (hash-get *terminal-copy-mode* buf))) (def (vterm-native-session buf) "Return the native QTerminalSession backing BUF, or #f." (and buf (hash-get *terminal-widget-map* buf))) @@ -1195,6 +1199,7 @@ [text (qt-terminal-get-screen-text session)]) (qt-plain-text-edit-set-read-only! ed #f) (qt-plain-text-edit-set-text! ed text) + (qt-terminal-apply-screen-styles! session ed) (qt-plain-text-edit-set-read-only! ed #t) (qt-stacked-widget-set-current-widget! container ed) (qt-widget-set-focus! ed) @@ -1202,7 +1207,7 @@ (hash-put! *terminal-copy-mode* buf #t) (echo-message! echo - "Copy mode: move/select text, then vterm-copy-mode-done")))] + "Copy mode: select text, RET or vterm-copy-mode-done copies")))] [(terminal-buffer? buf) (hash-put! *terminal-copy-mode* buf #t) (qt-plain-text-edit-set-read-only! ed #t) --- a/lib/jerboa-emacs/qt/commands.sls +++ b/lib/jerboa-emacs/qt/commands.sls @@ -5,35 +5,36 @@ (library (jerboa-emacs qt commands) (export qt-register-all-commands! dired-open-directory! qt-open-image-inline! *qt-app-ptr* *terminal-widget-map* - *terminal-views-map* terminal-view-for-container - terminal-ensure-view! qt-kill-ring-push! *isearch-active* - isearch-handle-key! isearch-start-with-text! - *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!) + *terminal-views-map* terminal-copy-mode-active? + terminal-view-for-container terminal-ensure-view! + qt-kill-ring-push! *isearch-active* isearch-handle-key! + isearch-start-with-text! *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 --- a/lib/jerboa-emacs/qt/sci-shim.sls +++ b/lib/jerboa-emacs/qt/sci-shim.sls @@ -141,10 +141,10 @@ qt-terminal-connect-fd! qt-terminal-send-key-event! qt-terminal-send-input! qt-terminal-is-running? qt-terminal-get-screen-text qt-terminal-interrupt! - qt-terminal-set-font! qt-terminal-set-colors! - qt-terminal-focus! qt-terminal-widget - qt-terminal-view-create qt-terminal-view-destroy! - qt-terminal-view-focus!) + qt-terminal-apply-screen-styles! qt-terminal-set-font! + qt-terminal-set-colors! qt-terminal-focus! + 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 @@ -662,6 +662,13 @@ (void*) string) term)) + (def (qt-terminal-apply-screen-styles! term sci) + "Apply the terminal screen's cell colors to SCI after qt-terminal-get-screen-text." + ((foreign-procedure "qt_terminal_apply_screen_styles" + (void* void*) + void) + term + sci)) (def (qt-terminal-interrupt! term) "Send SIGINT to the terminal's child process." ((foreign-procedure "qt_terminal_interrupt" (void*) void) --- a/lib/jerboa-emacs/qt/window.sls +++ b/lib/jerboa-emacs/qt/window.sls @@ -172,6 +172,23 @@ (def (qt-safe-widget-set-focus! context widget) (qt-window-check-handle! context "widget" widget) (qt-widget-set-focus! widget)) + (def (qt-dispose-widget! context widget) + (qt-window-check-handle! context "widget" widget) + (let ([trash (qt-splitter-create QT_HORIZONTAL)]) + (qt-safe-widget-hide! context widget) + (qt-safe-splitter-add-widget! context trash widget) + (qt-safe-widget-hide! context trash) + (qt-safe-widget-destroy! context trash))) + (def (qt-dispose-window-container! context container) + (qt-window-pre-container-destroy! container) + (qt-dispose-widget! context container)) + (def (qt-focus-target-for-edit-window win) + (or (and *post-split-focus-fn* (*post-split-focus-fn* win)) + (qt-edit-window-editor win))) + (def (qt-focus-edit-window! context win) + (qt-safe-widget-set-focus! + context + (qt-focus-target-for-edit-window win))) (def (qt-safe-widget-set-style-sheet! context widget style) (qt-window-check-handle! context "widget" widget) (qt-widget-set-style-sheet! widget style)) @@ -754,14 +771,9 @@ saved-w saved-h)) (when 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-focus-edit-window! + "qt-frame-do-split!" + (list-ref (qt-frame-windows fr) (qt-frame-current-idx fr)))) (qt-frame-update-visual-indicators! fr) (when main-win (qt-safe-widget-set-updates-enabled! @@ -831,14 +843,14 @@ (when (and parent-spl (not (eq? parent-spl (qt-frame-splitter fr)))) + (when container + (qt-window-pre-container-destroy! container)) (set! container #f) - (qt-safe-widget-destroy! + (qt-dispose-widget! "qt-frame-delete-window!" parent-spl)))))) (when container - (qt-window-pre-container-destroy! container) - (qt-safe-widget-hide! "qt-frame-delete-window!" container) - (qt-safe-widget-destroy! + (qt-dispose-window-container! "qt-frame-delete-window!" container)) (qt-frame-windows-set! @@ -852,9 +864,7 @@ (let ([new-win (list-ref (qt-frame-windows fr) (qt-frame-current-idx fr))]) - (qt-safe-widget-set-focus! - "qt-frame-delete-window!" - (qt-edit-window-editor new-win))) + (qt-focus-edit-window! "qt-frame-delete-window!" new-win)) (qt-frame-update-visual-indicators! fr) (qt-validate-window-tree! fr @@ -880,26 +890,18 @@ (let ([ed (qt-edit-window-editor win)] [container (qt-edit-window-container win)]) (hash-remove! *editor-window-map* ed) - (qt-window-pre-container-destroy! container) - (qt-safe-widget-hide! - "qt-frame-delete-other-windows!" - container) - (qt-safe-widget-destroy! + (qt-dispose-window-container! "qt-frame-delete-other-windows!" container)))) all-wins) (for-each (lambda (spl) - (qt-safe-widget-destroy! - "qt-frame-delete-other-windows!" - spl)) + (qt-dispose-widget! "qt-frame-delete-other-windows!" spl)) sub-spls) (qt-frame-root-set! fr (make-split-leaf cur)) (qt-frame-windows-set! fr (list cur)) (qt-frame-current-idx-set! fr 0) - (qt-safe-widget-set-focus! - "qt-frame-delete-other-windows!" - (qt-edit-window-editor cur)) + (qt-focus-edit-window! "qt-frame-delete-other-windows!" cur) (qt-frame-update-visual-indicators! fr) (qt-validate-window-tree! fr @@ -933,9 +935,7 @@ (let ([win (list-ref (qt-frame-windows fr) (qt-frame-current-idx fr))]) - (qt-safe-widget-set-focus! - "qt-frame-other-window!" - (qt-edit-window-editor win))) + (qt-focus-edit-window! "qt-frame-other-window!" win)) (qt-frame-update-visual-indicators! fr) (qt-validate-window-tree! fr --- a/scripts/patch-jerboa-std-compat.sh +++ b/scripts/patch-jerboa-std-compat.sh @@ -28,5 +28,44 @@ patch_import() { fi } +patch_tls_rustls_stub_loader() { + file="$libdir/std/net/tls-rustls.ss" + + [ -f "$file" ] || return 0 + if grep -q "jemacs_tls_stubs" "$file"; then + return 0 + fi + + tmp="${file}.tmp.$$" + awk ' + $0 == " ;; ========== FFI declarations ==========" { + print " ;; Dynamic editor/test runs may not link Rust rustls symbols into" + print " ;; libjerboa_native. Load the local no-op TLS symbol shim before" + print " ;; binding foreign procedures so imports that do not use TLS still work." + print " (def _jemacs-tls-stub-loaded" + print " (let* ([mt (symbol->string (machine-type))]" + print " [ext (if (and (>= (string-length mt) 3)" + print " (string=? (substring mt (- (string-length mt) 3) (string-length mt)) \"osx\"))" + print " \"dylib\"" + print " \"so\")]" + print " [dir (or (getenv \"JEMACS_TLS_STUB_DIR\")" + print " (getenv \"JERBOA_EMACS_SUPPORT\")" + print " (getenv \"JEMACS_BINARY_DIR\")" + print " \".\")])" + print " (try (begin (load-shared-object (string-append dir \"/jemacs_tls_stubs.\" ext)) #t)" + print " (catch (e) #f))))" + print "" + } + { print } + ' "$file" > "$tmp" + mv "$tmp" "$file" + + if ! grep -q "jemacs_tls_stubs" "$file"; then + echo "patch-jerboa-std-compat: failed to patch $file" >&2 + exit 1 + fi +} + patch_import "$libdir/std/misc/lru-cache.ss" patch_import "$libdir/std/misc/rbtree.ss" +patch_tls_rustls_stub_loader --- a/src/jerboa-emacs/editor-core.ss +++ b/src/jerboa-emacs/editor-core.ss @@ -720,6 +720,53 @@ (let ((pos (editor-get-current-pos ed))) (editor-set-selection ed pos pos))) +(def (editor-word-char? ch) + (or (char-alphabetic? ch) + (char-numeric? ch) + (char=? ch #\_))) + +(def (editor-word-right-pos text pos) + (let ((len (string-length text))) + (let skip-word ((i pos)) + (cond + ((>= i len) len) + ((editor-word-char? (string-ref text i)) + (skip-word (+ i 1))) + (else + (let skip-space ((j i)) + (cond + ((>= j len) len) + ((not (editor-word-char? (string-ref text j))) + (skip-space (+ j 1))) + (else j)))))))) + +(def (editor-word-left-pos text pos) + (let skip-space ((i (max 0 (- pos 1)))) + (cond + ((<= pos 0) 0) + ((and (> i 0) (not (editor-word-char? (string-ref text i)))) + (skip-space (- i 1))) + ((not (editor-word-char? (string-ref text i))) 0) + (else + (let skip-word ((j i)) + (cond + ((<= j 0) 0) + ((editor-word-char? (string-ref text (- j 1))) + (skip-word (- j 1))) + (else j))))))) + +(def (editor-move-word-right! ed) + (let* ((text (editor-get-text ed)) + (len (string-length text)) + (pos (min (editor-get-current-pos ed) len))) + (editor-goto-pos ed (editor-word-right-pos text pos)))) + +(def (editor-move-word-left! ed) + (let* ((text (editor-get-text ed)) + (len (string-length text)) + (pos (min (editor-get-current-pos ed) len))) + (editor-goto-pos ed (editor-word-left-pos text pos)))) + (def (cmd-forward-char app) (let ((n (get-prefix-arg app)) (ed (current-editor app))) (collapse-selection-to-caret! ed) @@ -832,26 +879,30 @@ (def (cmd-forward-word app) (let ((n (get-prefix-arg app)) (ed (current-editor app))) + (collapse-selection-to-caret! ed) (if (>= n 0) - (let loop ((i 0)) (when (< i n) (editor-send-key ed SCK_RIGHT ctrl: #t) (loop (+ i 1)))) - (let loop ((i 0)) (when (< i (- n)) (editor-send-key ed SCK_LEFT ctrl: #t) (loop (+ i 1))))) + (let loop ((i 0)) (when (< i n) (editor-move-word-right! ed) (loop (+ i 1)))) + (let loop ((i 0)) (when (< i (- n)) (editor-move-word-left! ed) (loop (+ i 1))))) (update-mark-region! app ed))) (def (cmd-backward-word app) (let ((n (get-prefix-arg app)) (ed (current-editor app))) + (collapse-selection-to-caret! ed) (if (>= n 0) - (let loop ((i 0)) (when (< i n) (editor-send-key ed SCK_LEFT ctrl: #t) (loop (+ i 1)))) - (let loop ((i 0)) (when (< i (- n)) (editor-send-key ed SCK_RIGHT ctrl: #t) (loop (+ i 1))))) + (let loop ((i 0)) (when (< i n) (editor-move-word-left! ed) (loop (+ i 1)))) + (let loop ((i 0)) (when (< i (- n)) (editor-move-word-right! ed) (loop (+ i 1))))) (update-mark-region! app ed))) (def (cmd-beginning-of-buffer app) (let ((ed (current-editor app))) - (editor-send-key ed SCK_HOME ctrl: #t) + (collapse-selection-to-caret! ed) + (editor-goto-pos ed 0) (update-mark-region! app ed))) (def (cmd-end-of-buffer app) (let ((ed (current-editor app))) - (editor-send-key ed SCK_END ctrl: #t) + (collapse-selection-to-caret! ed) + (editor-goto-pos ed (send-message ed SCI_GETLENGTH 0 0)) (update-mark-region! app ed))) (def (cmd-scroll-down app) --- a/src/jerboa-emacs/editor-extra-regs2.ss +++ b/src/jerboa-emacs/editor-extra-regs2.ss @@ -20,7 +20,7 @@ cmd-info-reader cmd-project-tree-git cmd-open-line) (only-in :jerboa-emacs/editor-text cmd-delete-blank-lines cmd-call-last-kbd-macro cmd-name-last-kbd-macro) - (only-in :jerboa-emacs/editor-ui cmd-eval-expression) + (only-in :jerboa-emacs/editor-ui cmd-eval-expression cmd-keyboard-quit) (only-in :jerboa-emacs/editor-cmds-c cmd-imenu cmd-electric-newline-and-indent) (only-in :jerboa-emacs/helm-commands cmd-helm-mark-ring cmd-helm-M-x cmd-helm-mini @@ -108,7 +108,7 @@ cmd-sudo-edit cmd-tabify cmd-untabify cmd-view-lossage cmd-what-page cmd-copy-line-number cmd-delete-frame cmd-describe-mode cmd-goto-line-relative cmd-insert-lorem-ipsum cmd-make-frame - cmd-narrow-to-defun cmd-suspend-frame cmd-write-region) + cmd-keyboard-quit cmd-narrow-to-defun cmd-suspend-frame cmd-write-region) (except-in :jerboa-emacs/editor-extra-regs cmd-count-lines-page cmd-delete-directory cmd-how-many cmd-iedit-mode cmd-other-frame) --- a/src/jerboa-emacs/qt/app.ss +++ b/src/jerboa-emacs/qt/app.ss @@ -1113,6 +1113,13 @@ (qt-echo-draw! (app-state-echo app) echo-label)) (*snake-active* (snake-handle-key! app code mods text)) + ((and (or (= code QT_KEY_RETURN) (= code QT_KEY_ENTER)) + (zero? (bitwise-and mods QT_MOD_CTRL)) + (zero? (bitwise-and mods QT_MOD_ALT)) + (terminal-copy-mode-active? + (qt-current-buffer (app-state-frame app)))) + (execute-command! app 'vterm-copy-done) + (qt-refresh-after-key! app echo-label "vterm-copy-done")) (else ;; Normal key processing — with chord detection (letrec @@ -1617,17 +1624,23 @@ (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. + ;; Install window-focus hook: when a selected window shows a live + ;; terminal page, focus that QTerminalView rather than the hidden editor + ;; page behind it. Returns #f for non-terminal buffers and copy-mode + ;; terminal buffers so the window manager focuses the 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)))) + (lambda (win) + (let* ((buf (qt-edit-window-buffer win)) + (container (qt-edit-window-container win)) + (session (and buf (hash-get *terminal-widget-map* buf))) + (view (and session + (> (qt-stacked-widget-current-index container) 0) + (terminal-view-for-container buf container)))) (and session - (terminal-view-for-container - buf (qt-edit-window-container new-win)))))) + view + (begin + (qt-terminal-view-focus! view) + view))))) (qt-startup-trace "key handler setup done") ;; ================================================================ --- a/src/jerboa-emacs/qt/commands-config.ss +++ b/src/jerboa-emacs/qt/commands-config.ss @@ -998,6 +998,10 @@ modified so the next save uses the new encoding." ;; Track which terminal buffers are in copy mode (def *terminal-copy-mode* (make-hash-table)) +(def (terminal-copy-mode-active? buf) + "Return true when BUF is currently showing a frozen vterm copy buffer." + (and buf (hash-get *terminal-copy-mode* buf))) + (def (vterm-native-session buf) "Return the native QTerminalSession backing BUF, or #f." (and buf (hash-get *terminal-widget-map* buf))) @@ -1022,12 +1026,13 @@ modified so the next save uses the new encoding." (text (qt-terminal-get-screen-text session))) (qt-plain-text-edit-set-read-only! ed #f) ;; allow the screen dump in (qt-plain-text-edit-set-text! ed text) + (qt-terminal-apply-screen-styles! session ed) (qt-plain-text-edit-set-read-only! ed #t) (qt-stacked-widget-set-current-widget! container ed) (qt-widget-set-focus! ed) (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) (hash-put! *terminal-copy-mode* buf #t) - (echo-message! echo "Copy mode: move/select text, then vterm-copy-mode-done")))) + (echo-message! echo "Copy mode: select text, RET or vterm-copy-mode-done copies")))) ;; Legacy line-terminal: read-only toggle on the editor (old behavior). ((terminal-buffer? buf) (hash-put! *terminal-copy-mode* buf #t) --- a/src/jerboa-emacs/qt/commands.ss +++ b/src/jerboa-emacs/qt/commands.ss @@ -8,6 +8,7 @@ *qt-app-ptr* *terminal-widget-map* *terminal-views-map* + terminal-copy-mode-active? terminal-view-for-container terminal-ensure-view! qt-kill-ring-push! --- a/src/jerboa-emacs/qt/sci-shim.ss +++ b/src/jerboa-emacs/qt/sci-shim.ss @@ -142,6 +142,7 @@ qt-terminal-connect-fd! qt-terminal-send-key-event! qt-terminal-send-input! qt-terminal-is-running? qt-terminal-get-screen-text qt-terminal-interrupt! + qt-terminal-apply-screen-styles! qt-terminal-set-font! qt-terminal-set-colors! qt-terminal-focus! qt-terminal-widget qt-terminal-view-create qt-terminal-view-destroy! qt-terminal-view-focus!) @@ -649,6 +650,10 @@ "Return the visible terminal screen as plain text (for copy mode)." ((foreign-procedure "qt_terminal_get_screen_text" (void*) string) term)) +(def (qt-terminal-apply-screen-styles! term sci) + "Apply the terminal screen's cell colors to SCI after qt-terminal-get-screen-text." + ((foreign-procedure "qt_terminal_apply_screen_styles" (void* void*) void) term sci)) + (def (qt-terminal-interrupt! term) "Send SIGINT to the terminal's child process." ((foreign-procedure "qt_terminal_interrupt" (void*) void) term)) --- a/src/jerboa-emacs/qt/window.ss +++ b/src/jerboa-emacs/qt/window.ss @@ -43,7 +43,7 @@ qt-window-set-app-ptr! ;; Hook called before any window container is destroyed qt-window-set-pre-container-destroy-fn! - ;; Hook to override focus target after a split (e.g. terminal views) + ;; Hook to override focus target for a selected window (e.g. terminal views) qt-window-set-post-split-focus-fn!) (import :std/sugar @@ -70,10 +70,10 @@ (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. +;; Called with (window) when that window should receive focus. Returns a widget +;; to focus, or #f to fall back to the editor. Registered by the terminal code so +;; a selected terminal pane focuses the visible QTerminalView, not the hidden +;; editor page behind it. The historical setter name is kept for callers. (def *post-split-focus-fn* #f) (def (qt-window-set-post-split-focus-fn! fn) @@ -239,6 +239,31 @@ (qt-window-check-handle! context "widget" widget) (qt-widget-set-focus! widget)) +(def (qt-dispose-widget! context widget) + (qt-window-check-handle! context "widget" widget) + (let ((trash (qt-splitter-create QT_HORIZONTAL))) + ;; Moving WIDGET into a detached splitter removes it from its current + ;; splitter immediately. qt-widget-destroy! uses deleteLater(), so relying + ;; on destruction alone leaves QSplitter counts stale until Qt processes + ;; deferred-delete events. + (qt-safe-widget-hide! context widget) + (qt-safe-splitter-add-widget! context trash widget) + (qt-safe-widget-hide! context trash) + (qt-safe-widget-destroy! context trash))) + +(def (qt-dispose-window-container! context container) + (qt-window-pre-container-destroy! container) + (qt-dispose-widget! context container)) + +(def (qt-focus-target-for-edit-window win) + (or (and *post-split-focus-fn* + (*post-split-focus-fn* win)) + (qt-edit-window-editor win))) + +(def (qt-focus-edit-window! context win) + (qt-safe-widget-set-focus! context + (qt-focus-target-for-edit-window win))) + (def (qt-safe-widget-set-style-sheet! context widget style) (qt-window-check-handle! context "widget" widget) (qt-widget-set-style-sheet! widget style)) @@ -731,11 +756,9 @@ ;; behind the QTerminalView, so let the registered hook override the target ;; to focus the new view instead of the (invisible) editor. (when 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-focus-edit-window! "qt-frame-do-split!" + (list-ref (qt-frame-windows fr) + (qt-frame-current-idx fr)))) ;; Update visual indicators (qt-frame-update-visual-indicators! fr) ;; Re-enable updates — single @@ -813,25 +836,23 @@ ;; means destroying parent-spl also destroys container. Track this ;; to avoid the double-free in the explicit container destroy below. (when (and parent-spl (not (eq? parent-spl (qt-frame-splitter fr)))) + (when container + (qt-window-pre-container-destroy! container)) (set! container #f) ;; cleared — Qt will destroy it via parent-spl - (qt-safe-widget-destroy! "qt-frame-delete-window!" - parent-spl)))))) + (qt-dispose-widget! "qt-frame-delete-window!" parent-spl)))))) ;; Destroy the deleted window's Qt container. ;; Skipped when container was set to #f above (already destroyed via parent-spl). (when container - (qt-window-pre-container-destroy! container) - (qt-safe-widget-hide! "qt-frame-delete-window!" container) - (qt-safe-widget-destroy! "qt-frame-delete-window!" container)) + (qt-dispose-window-container! "qt-frame-delete-window!" container)) ;; Update flat windows list and current-idx (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) + ;; Restore focus to the new current window (destroying widgets may lose it) (let ((new-win (list-ref (qt-frame-windows fr) (qt-frame-current-idx fr)))) - (qt-safe-widget-set-focus! "qt-frame-delete-window!" - (qt-edit-window-editor new-win))) + (qt-focus-edit-window! "qt-frame-delete-window!" new-win)) ;; Update visual indicators (qt-frame-update-visual-indicators! fr) (qt-validate-window-tree! fr "after qt-frame-delete-window!")))) @@ -852,24 +873,21 @@ (let ((ed (qt-edit-window-editor win)) (container (qt-edit-window-container win))) (hash-remove! *editor-window-map* ed) - (qt-window-pre-container-destroy! container) - (qt-safe-widget-hide! "qt-frame-delete-other-windows!" - container) - (qt-safe-widget-destroy! "qt-frame-delete-other-windows!" - container)))) + (qt-dispose-window-container! + "qt-frame-delete-other-windows!" + container)))) all-wins) ;; 3. Destroy sub-splitters (innermost first, avoiding double-destroy) (for-each (lambda (spl) - (qt-safe-widget-destroy! "qt-frame-delete-other-windows!" spl)) + (qt-dispose-widget! "qt-frame-delete-other-windows!" spl)) sub-spls) ;; 4. Update logical tree and flat list (set! (qt-frame-root fr) (make-split-leaf cur)) (set! (qt-frame-windows fr) (list cur)) (set! (qt-frame-current-idx fr) 0) - ;; 5. Restore focus to the surviving editor (reparenting may lose it) - (qt-safe-widget-set-focus! "qt-frame-delete-other-windows!" - (qt-edit-window-editor cur)) + ;; 5. Restore focus to the surviving window (reparenting may lose it) + (qt-focus-edit-window! "qt-frame-delete-other-windows!" cur) ;; 6. Update visual indicators (qt-frame-update-visual-indicators! fr) (qt-validate-window-tree! fr "after qt-frame-delete-other-windows!"))) @@ -902,10 +920,9 @@ (let ((n (length (qt-frame-windows fr)))) (set! (qt-frame-current-idx fr) (modulo (+ (qt-frame-current-idx fr) 1) n)) - ;; Give keyboard focus to the new active editor + ;; Give keyboard focus to the new active window. (let ((win (list-ref (qt-frame-windows fr) (qt-frame-current-idx fr)))) - (qt-safe-widget-set-focus! "qt-frame-other-window!" - (qt-edit-window-editor win))) + (qt-focus-edit-window! "qt-frame-other-window!" win)) ;; Update visual indicators (qt-frame-update-visual-indicators! fr) (qt-validate-window-tree! fr "after qt-frame-other-window!"))) --- a/support/jemacs_tls_stubs.c +++ b/support/jemacs_tls_stubs.c @@ -2,6 +2,23 @@ #include <stddef.h> #include <stdint.h> +#include <string.h> + +size_t jerboa_last_error(uint8_t *buf, size_t len) { + const char *msg = "TLS not available in jemacs test stub"; + size_t msg_len = strlen(msg); + size_t copy_len = msg_len; + + if (len == 0) { + return msg_len; + } + if (copy_len >= len) { + copy_len = len - 1; + } + memcpy(buf, msg, copy_len); + buf[copy_len] = 0; + return copy_len; +} uint64_t jerboa_tls_server_new(const char *cert, const char *key) { (void)cert; --- a/support/vendor-overrides/qt_shim.cpp +++ b/support/vendor-overrides/qt_shim.cpp @@ -8164,6 +8164,36 @@ QWidget* QTerminalSession::representativeWidget() { typedef void* qt_terminal_t; // QTerminalSession* typedef void* qt_terminal_view_t; // QTerminalView* (a QWidget*) +static long term_color_to_sci(const QColor& c) { + return (long)(c.red() | (c.green() << 8) | (c.blue() << 16)); +} + +static QColor term_resolve_vterm_color(QTerminalSession* s, + VTermScreen* screen, + const VTermColor& color, + bool foreground) { + if ((foreground && VTERM_COLOR_IS_DEFAULT_FG(&color)) || + (!foreground && VTERM_COLOR_IS_DEFAULT_BG(&color))) { + return foreground ? s->m_default_fg : s->m_default_bg; + } + + VTermColor c = color; + if (VTERM_COLOR_IS_INDEXED(&c)) + vterm_screen_convert_color_to_rgb(screen, &c); + return QColor(c.rgb.red, c.rgb.green, c.rgb.blue); +} + +struct TerminalStyleCell { + int byte_start; + int byte_len; + long fg; + long bg; + bool bold; + bool italic; + bool underline; + bool space; +}; + // Create a terminal SESSION (model only — no view yet). The parent argument is // ignored (the session is a parentless QObject); kept for ABI compatibility. extern "C" qt_terminal_t qt_terminal_create(qt_widget_t parent) { @@ -8302,6 +8332,121 @@ extern "C" const char* qt_terminal_get_screen_text(qt_terminal_t term) { })()); } +extern "C" void qt_terminal_apply_screen_styles(qt_terminal_t term, + qt_scintilla_t sci) { + QT_NULL_CHECK_VOID(term); + QT_NULL_CHECK_VOID(sci); + QT_VOID( + auto* session = static_cast<QTerminalSession*>(term); + auto* editor = static_cast<QsciScintilla*>(sci); + VTermScreen* screen = session->m_screen; + if (screen) { + const int first_style = 80; + const int max_style = 249; + int next_style = first_style; + std::unordered_map<unsigned long long, int> style_map; + + auto style_for = [&](long fg, long bg, bool bold, + bool italic, bool underline) -> int { + unsigned long long key = + ((unsigned long long)(fg & 0xFFFFFF) << 28) | + ((unsigned long long)(bg & 0xFFFFFF) << 4) | + (bold ? 1ULL : 0ULL) | + (italic ? 2ULL : 0ULL) | + (underline ? 4ULL : 0ULL); + auto it = style_map.find(key); + if (it != style_map.end()) return it->second; + if (next_style > max_style) return QsciScintillaBase::STYLE_DEFAULT; + + int style = next_style++; + style_map[key] = style; + editor->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, + style, fg); + editor->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK, + style, bg); + editor->SendScintilla(QsciScintillaBase::SCI_STYLESETBOLD, + style, bold ? 1L : 0L); + editor->SendScintilla(QsciScintillaBase::SCI_STYLESETITALIC, + style, italic ? 1L : 0L); + editor->SendScintilla(QsciScintillaBase::SCI_STYLESETUNDERLINE, + style, underline ? 1L : 0L); + return style; + }; + + std::vector<std::vector<TerminalStyleCell>> rows; + rows.reserve(session->m_rows); + int last_nonempty_row = -1; + + for (int row = 0; row < session->m_rows; ++row) { + std::vector<TerminalStyleCell> cells; + int byte_pos = 0; + for (int col = 0; col < session->m_cols; ++col) { + VTermPos pos = { row, col }; + VTermScreenCell cell; + vterm_screen_get_cell(screen, pos, &cell); + + QString text; + bool space = false; + if (cell.chars[0] != 0) { + char32_t cp = static_cast<char32_t>(cell.chars[0]); + text = QString::fromUcs4(&cp, 1); + } else { + text = QLatin1Char(' '); + space = true; + } + + QByteArray utf8 = text.toUtf8(); + int utf8_len = static_cast<int>(utf8.size()); + QColor fg = term_resolve_vterm_color(session, screen, cell.fg, true); + QColor bg = term_resolve_vterm_color(session, screen, cell.bg, false); + if (cell.attrs.reverse) std::swap(fg, bg);