From 46bb40563b4f7b82aa83ea7deb2647494f73db6d Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Mon, 27 Nov 2017 09:55:47 -0500 Subject: Use initial states of parent major modes Teach evil-initial-state to look at aliases for a mode when they exist and to handle nil for modes Search parent modes (and their aliases) for defined initial states in evil-initial-state-for-buffer. One effect is that (evil-set-initial-state 'special-mode 'motion) now makes motion state the default for all major modes that derive from special mode and don't have defaults set for them. --- evil-core.el | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/evil-core.el b/evil-core.el index 125c166..271dfb0 100644 --- a/evil-core.el +++ b/evil-core.el @@ -283,20 +283,41 @@ See also `evil-initial-state'." (when (setq mode (evil-initial-state mode)) (throw 'done mode))))) (evil-initial-state major-mode) + ;; Check parent modes. Similar to `derived-mode-p' + (catch 'state + (let ((mode major-mode) + checked-modes) + (while (and mode (symbolp mode)) + (when (memq mode checked-modes) + (message "Circular reference detected in ancestors of %s\n%s" + major-mode checked-modes) + (throw 'state nil)) + (let ((state (evil-initial-state mode))) + (when state + (throw 'state state))) + (push mode checked-modes) + (setq mode (get mode 'derived-mode-parent))) + nil)) default))) (defun evil-initial-state (mode &optional default) - "Return the Evil state to use for MODE. + "Return the Evil state to use for MODE or its alias. Returns DEFAULT if no initial state is associated with MODE. The initial state for a mode can be set with `evil-set-initial-state'." - (let (state modes) - (catch 'done - (dolist (entry (evil-state-property t :modes) default) - (setq state (car entry) - modes (symbol-value (cdr entry))) - (when (memq mode modes) - (throw 'done state)))))) + (when mode + (let ((mode-alias (let ((func (symbol-function mode))) + (when (symbolp func) + func))) + state modes) + (catch 'done + (dolist (entry (evil-state-property t :modes) default) + (setq state (car entry) + modes (symbol-value (cdr entry))) + (when (or (memq mode modes) + (and mode-alias + (memq mode-alias modes))) + (throw 'done state))))))) (defun evil-set-initial-state (mode state) "Set the initial state for MODE to STATE. -- cgit v1.0 From d2d4748daa65f97b918522e76d15dcebccc2691b Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Wed, 3 Jan 2018 22:40:13 -0500 Subject: Throw error on circular major-mode parent reference --- evil-core.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evil-core.el b/evil-core.el index 271dfb0..338cdb3 100644 --- a/evil-core.el +++ b/evil-core.el @@ -289,8 +289,8 @@ See also `evil-initial-state'." checked-modes) (while (and mode (symbolp mode)) (when (memq mode checked-modes) - (message "Circular reference detected in ancestors of %s\n%s" - major-mode checked-modes) + (error "Circular reference detected in ancestors of %s\n%s" + major-mode checked-modes) (throw 'state nil)) (let ((state (evil-initial-state mode))) (when state -- cgit v1.0 From f59b56ec48460b8d1479076dbc809db02b6fa9da Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Thu, 25 Jan 2018 08:02:27 -0500 Subject: Add tests for evil-initial-state --- evil-tests.el | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/evil-tests.el b/evil-tests.el index 75b28b4..50edebd 100644 --- a/evil-tests.el +++ b/evil-tests.el @@ -8257,6 +8257,48 @@ when an error stops the execution of the macro" ; should not raise an "Selecting deleted buffer" error (evil-visual-update-x-selection buf)))) +;;; Core + +(ert-deftest evil-test-initial-state () + "Test `evil-initial-state'" + :tags '(evil core) + (define-derived-mode test-1-mode prog-mode "Test1") + (define-derived-mode test-2-mode test-1-mode "Test2") + (evil-set-initial-state 'test-1-mode 'insert) + (ert-info ("Check default state") + (should (eq (evil-initial-state 'prog-mode 'normal) 'normal))) + (ert-info ("Basic functionality 1") + (should (eq (evil-initial-state 'test-1-mode) 'insert))) + (ert-info ("Basic functionality 2") + (evil-test-buffer + "abc\ndef\n" + (test-1-mode) + (should (eq evil-state 'insert)))) + (ert-info ("Inherit initial state from a parent") + (evil-test-buffer + "abc\ndef\n" + (test-2-mode) + (should (eq evil-state 'insert)))) + (evil-set-initial-state 'test-1-mode nil) + (ert-info ("Check for inheritance loops") + (evil-test-buffer + "abc\ndef\n" + (unwind-protect + (let ((major-mode 'test-2-mode)) + (put 'test-1-mode 'derived-mode-parent 'test-2-mode) + ;; avoid triggering all of the hooks here, some of which might get + ;; caught in loops depending on the environment. settings major-mode + ;; is sufficient for `evil-initial-state-for-buffer' to work. + (should-error (evil-initial-state-for-buffer))) + (put 'test-1-mode 'derived-mode-parent 'prog-mode)))) + (defalias 'test-1-alias-mode 'test-1-mode) + (define-derived-mode test-3-mode test-1-alias-mode "Test3") + (evil-set-initial-state 'test-1-mode 'insert) + (ert-info ("Check inheritance from major mode aliases") + "abc\ndef\n" + (test-3-mode) + (should (eq evil-state 'insert)))) + (provide 'evil-tests) ;;; evil-tests.el ends here -- cgit v1.0 From fa512647366ff62a190f0721e6161b4279f128fe Mon Sep 17 00:00:00 2001 From: Justin Burkett Date: Sat, 6 Jan 2018 21:22:24 -0500 Subject: Teach evil-initial-state about parent modes Previously this was done in evil-initial-state-for-buffer, but it's easier to recursively follow all parent branches (including those from aliases) within evil-initial-state. --- evil-core.el | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/evil-core.el b/evil-core.el index 338cdb3..eb23eed 100644 --- a/evil-core.el +++ b/evil-core.el @@ -282,42 +282,42 @@ See also `evil-initial-state'." (when (and (boundp mode) (symbol-value mode)) (when (setq mode (evil-initial-state mode)) (throw 'done mode))))) - (evil-initial-state major-mode) - ;; Check parent modes. Similar to `derived-mode-p' - (catch 'state - (let ((mode major-mode) - checked-modes) - (while (and mode (symbolp mode)) - (when (memq mode checked-modes) - (error "Circular reference detected in ancestors of %s\n%s" - major-mode checked-modes) - (throw 'state nil)) - (let ((state (evil-initial-state mode))) - (when state - (throw 'state state))) - (push mode checked-modes) - (setq mode (get mode 'derived-mode-parent))) - nil)) + (evil-initial-state major-mode nil t) default))) -(defun evil-initial-state (mode &optional default) +(defun evil-initial-state (mode &optional default follow-parent checked-modes) "Return the Evil state to use for MODE or its alias. Returns DEFAULT if no initial state is associated with MODE. The initial state for a mode can be set with -`evil-set-initial-state'." - (when mode +`evil-set-initial-state'. + +If FOLLOW-PARENT is non-nil, also check parent modes of MODE and +its alias. CHECKED-MODES is used internally and should not be set +initially." + (cond + ((and mode (symbolp mode) (memq mode checked-modes)) + (error "Circular reference detected in ancestors of %s\n%s" + major-mode checked-modes)) + ((and mode (symbolp mode)) (let ((mode-alias (let ((func (symbol-function mode))) (when (symbolp func) func))) state modes) - (catch 'done - (dolist (entry (evil-state-property t :modes) default) - (setq state (car entry) - modes (symbol-value (cdr entry))) - (when (or (memq mode modes) - (and mode-alias - (memq mode-alias modes))) - (throw 'done state))))))) + (or + (catch 'done + (dolist (entry (evil-state-property t :modes) default) + (setq state (car entry) + modes (symbol-value (cdr entry))) + (when (or (memq mode modes) + (and mode-alias + (memq mode-alias modes))) + (throw 'done state)))) + (when follow-parent + (evil-initial-state (get mode 'derived-mode-parent) + nil t (cons mode checked-modes))) + (when follow-parent + (evil-initial-state (get mode-alias 'derived-mode-parent) + nil t (cons mode-alias checked-modes)))))))) (defun evil-set-initial-state (mode state) "Set the initial state for MODE to STATE. -- cgit v1.0