diff --git a/lisp/ghostel.el b/lisp/ghostel.el index 8d0287fd..05a7996c 100644 --- a/lisp/ghostel.el +++ b/lisp/ghostel.el @@ -5015,6 +5015,14 @@ for both native and Emacs PTY paths." ;;; Major mode +(defun ghostel--change-major-mode-guard () + "Signal a `user-error' while the buffer's terminal process is live. +A mode change runs `kill-all-local-variables', wiping the buffer-locals +the native module and PTY depend on; once the process is dead the mode +may change freely (`ghostel-compile' finalize relies on this)." + (when (process-live-p ghostel--process) + (user-error "Cannot change major mode in a live ghostel buffer"))) + (define-derived-mode ghostel-mode fundamental-mode "Ghostel" "Major mode for Ghostel terminal emulator." (hack-dir-local-variables) @@ -5071,6 +5079,7 @@ for both native and Emacs PTY paths." (add-hook 'activate-mark-hook #'ghostel--mark-activated nil t) (add-hook 'isearch-mode-end-hook #'ghostel-maybe-leave-input nil t) (add-hook 'kill-buffer-query-functions #'ghostel--kill-buffer-query nil t) + (add-hook 'change-major-mode-hook #'ghostel--change-major-mode-guard nil t) ;; Show the hyperlink URI at point in eldoc. (add-hook 'eldoc-documentation-functions #'ghostel--eldoc-link nil t) diff --git a/test/ghostel-modes-test.el b/test/ghostel-modes-test.el index 9bc22e3d..cbb87532 100644 --- a/test/ghostel-modes-test.el +++ b/test/ghostel-modes-test.el @@ -16,6 +16,40 @@ (should (member 'ghostel-default (cdr (assq 'default face-remapping-alist)))))) +(ert-deftest ghostel-test-major-mode-change-blocked-while-live () + "Changing the major mode signals `user-error' while the terminal runs. +The switch is aborted before `kill-all-local-variables' wipes the +buffer-locals the native module and PTY depend on." + (let ((buf (generate-new-buffer " *ghostel-test-mode-guard*")) + (proc nil)) + (unwind-protect + (with-current-buffer buf + (ghostel-mode) + (setq proc (make-process :name "ghostel-test-mode-guard" + :command '("sleep" "30") + :buffer nil :noquery t)) + (setq-local ghostel--process proc) + (should-error (fundamental-mode) :type 'user-error) + (should (eq major-mode 'ghostel-mode)) + ;; Buffer-locals survived — the hook fired before any teardown. + (should (eq ghostel--process proc))) + (when (process-live-p proc) + (delete-process proc)) + (kill-buffer buf)))) + +(ert-deftest ghostel-test-major-mode-change-allowed-after-exit () + "With no live process the major mode may change freely. +`ghostel-compile' relies on this: it tears down the terminal, then +switches the buffer into its view mode." + (let ((buf (generate-new-buffer " *ghostel-test-mode-guard-dead*"))) + (unwind-protect + (with-current-buffer buf + (ghostel-mode) + (should (null ghostel--process)) + (fundamental-mode) + (should (eq major-mode 'fundamental-mode))) + (kill-buffer buf)))) + (ert-deftest ghostel-test-mode-shields-default-text-properties () "A global `default-text-properties' does not reach ghostel buffers. Its `line-spacing'/`line-height' fallback would inflate rendered rows