From 5fcbb19ebf9023ae7f26796cf9ccfa7be97347b6 Mon Sep 17 00:00:00 2001 From: Nikita Aleksandrov Date: Tue, 21 Apr 2026 02:17:30 +0300 Subject: [PATCH] Expose initial evil state as defcustom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `evil-ghostel-mode' hard-coded `(evil-set-initial-state 'ghostel-mode 'insert)' inside its activation body. Since the minor mode is wired to `ghostel-mode-hook', that call fires on every ghostel buffer creation — and runs before `evil-initialize' reads the initial state via `after-change-major-mode-hook'. Any user override (customize, setq, or direct `evil-set-initial-state' call) was silently clobbered on the next ghostel buffer. Replace the hard-coded call with `evil-ghostel-initial-state', a defcustom whose `:set' re-runs `evil-set-initial-state'. Apply the current value once at package load to cover the `setq-before-require' path where `defcustom' preserves the value without invoking `:set'. The evil registry is last-writer-wins, so users who prefer the raw API can still call `evil-set-initial-state' directly from `:config' without knowing about the option. Default remains `insert' to preserve existing behaviour. Add tests covering: load-time registration, customize-set updates, and activation not clobbering the initial-state setting (regression guard). --- evil-ghostel.el | 30 +++++++++++++++++++++++++++++- test/evil-ghostel-test.el | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/evil-ghostel.el b/evil-ghostel.el index c432d14..72975a0 100644 --- a/evil-ghostel.el +++ b/evil-ghostel.el @@ -27,6 +27,35 @@ (require 'evil) (require 'ghostel) +;; --------------------------------------------------------------------------- +;; Customization +;; --------------------------------------------------------------------------- + +(defgroup evil-ghostel nil + "Evil-mode integration for ghostel." + :group 'ghostel + :prefix "evil-ghostel-") + +(defcustom evil-ghostel-initial-state 'insert + "Initial evil state for new `ghostel-mode' buffers. +Setting this option via `customize-set-variable', `setopt', or the +Customize UI calls `evil-set-initial-state' so the change takes effect +immediately. Users who prefer the raw API can call +`evil-set-initial-state' directly from their config — the registry is +last-writer-wins." + :type '(choice (const :tag "Emacs" emacs) + (const :tag "Insert" insert) + (const :tag "Normal" normal) + (symbol :tag "Other state")) + :set (lambda (sym val) + (set-default-toplevel-value sym val) + (evil-set-initial-state 'ghostel-mode val))) + +;; Apply the current value at load. Covers the case where the user set +;; the variable with plain `setq' before loading the package — in that +;; path `defcustom' preserves the value without invoking `:set'. +(evil-set-initial-state 'ghostel-mode evil-ghostel-initial-state) + ;; --------------------------------------------------------------------------- ;; Guard predicate ;; --------------------------------------------------------------------------- @@ -334,7 +363,6 @@ state transitions." :keymap evil-ghostel-mode-map (if evil-ghostel-mode (progn - (evil-set-initial-state 'ghostel-mode 'insert) (evil-ghostel--escape-stay) (add-hook 'evil-normal-state-entry-hook #'evil-ghostel--normal-state-entry nil t) diff --git a/test/evil-ghostel-test.el b/test/evil-ghostel-test.el index 309b94c..562deda 100644 --- a/test/evil-ghostel-test.el +++ b/test/evil-ghostel-test.el @@ -81,6 +81,38 @@ Uses mocks for native functions." (should-not (memq 'evil-ghostel--insert-state-entry evil-insert-state-entry-hook)))) +;; ----------------------------------------------------------------------- +;; Test: initial-state defcustom +;; ----------------------------------------------------------------------- + +(ert-deftest evil-ghostel-test-initial-state-load-applied () + "Current value of `evil-ghostel-initial-state' is registered with evil at load." + (should (eq (evil-initial-state 'ghostel-mode) + evil-ghostel-initial-state))) + +(ert-deftest evil-ghostel-test-initial-state-custom-set-updates-registry () + "Setting the option via `customize-set-variable' updates evil's registry." + (let ((orig evil-ghostel-initial-state)) + (unwind-protect + (progn + (customize-set-variable 'evil-ghostel-initial-state 'emacs) + (should (eq (evil-initial-state 'ghostel-mode) 'emacs)) + (customize-set-variable 'evil-ghostel-initial-state 'normal) + (should (eq (evil-initial-state 'ghostel-mode) 'normal))) + (customize-set-variable 'evil-ghostel-initial-state orig)))) + +(ert-deftest evil-ghostel-test-mode-activation-preserves-initial-state () + "Enabling `evil-ghostel-mode' must not clobber the initial-state setting. +Regression guard: the minor-mode body used to call +`evil-set-initial-state' on every activation, overriding user config." + (let ((orig evil-ghostel-initial-state)) + (unwind-protect + (progn + (customize-set-variable 'evil-ghostel-initial-state 'emacs) + (evil-ghostel-test--with-evil-buffer + (should (eq (evil-initial-state 'ghostel-mode) 'emacs)))) + (customize-set-variable 'evil-ghostel-initial-state orig)))) + ;; ----------------------------------------------------------------------- ;; Test: escape-stay (evil-move-cursor-back disabled) ;; -----------------------------------------------------------------------