From 44add36e972ed22ded6cb89654986e80d1d8989e Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sat, 1 Jul 2023 16:05:34 -0400 Subject: [PATCH] (evil-with-delay): New macro, extracted from `evil-delay` Save some kittens by using a macro instead of using `eval`. This also exposes more code to the compiler, so should result in more efficient code and potentially better compiler warnings. * evil-common.el (evil-unquote): Delete unused function. (evil--with-delay): New function, extracted from `evil-delay`. (evil-with-delay): New macro, extracted from `evil-delay`. (evil-delay): Redefine using `evil-with-delay` and declare obsolete. * evil-states.el (evil-visual-activate-hook): * evil-core.el (evil-define-key): * evil-commands.el (evil-execute-in-normal-state): Use `evil-with-delay`. --- evil-commands.el | 68 +++++++++++++++++++++++++----------------------- evil-common.el | 50 +++++++++++++++++++++-------------- evil-core.el | 38 +++++++++++++++------------ evil-states.el | 21 +++++++-------- 4 files changed, 97 insertions(+), 80 deletions(-) diff --git a/evil-commands.el b/evil-commands.el index 8e4de2be..8d3db3db 100644 --- a/evil-commands.el +++ b/evil-commands.el @@ -5116,39 +5116,41 @@ Restore the disabled repeat hooks on insert-state exit." (defun evil-execute-in-normal-state () "Execute the next command in Normal state." (interactive) - (evil-delay '(not (memq this-command - '(nil - evil-execute-in-normal-state - evil-replace-state - evil-use-register - digit-argument - negative-argument - universal-argument - universal-argument-minus - universal-argument-more - universal-argument-other-key))) - `(with-current-buffer ,(current-buffer) - ;; If cursor was after EOL before CTRL-O and is now at EOL, - ;; put it after EOL. - (and (or (when evil--execute-normal-eol-pos - (= (1+ (point)) (save-excursion - (goto-char evil--execute-normal-eol-pos) - (set-marker evil--execute-normal-eol-pos nil) - (line-end-position)))) - (and (eq (or goal-column temporary-goal-column) most-positive-fixnum) - (memq this-command '(next-line previous-line)))) - (not (eolp)) - (not (memq this-command - '(evil-insert evil-beginning-of-line evil-first-non-blank))) - (forward-char)) - (unless (memq evil-state '(replace insert)) - (evil-change-state ',evil-state)) - (when (eq 'insert evil-state) - (remove-hook 'pre-command-hook #'evil-repeat-pre-hook) - (remove-hook 'post-command-hook #'evil-repeat-post-hook) - (add-hook 'evil-insert-state-exit-hook #'evil--restore-repeat-hooks)) - (setq evil-execute-normal-keys nil)) - 'post-command-hook) + (let ((buf (current-buffer)) + (state evil-state)) + (evil-with-delay (not (memq this-command + '(nil + evil-execute-in-normal-state + evil-replace-state + evil-use-register + digit-argument + negative-argument + universal-argument + universal-argument-minus + universal-argument-more + universal-argument-other-key))) + post-command-hook + (with-current-buffer buf + ;; If cursor was after EOL before CTRL-O and is now at EOL, + ;; put it after EOL. + (and (or (when evil--execute-normal-eol-pos + (= (1+ (point)) (save-excursion + (goto-char evil--execute-normal-eol-pos) + (set-marker evil--execute-normal-eol-pos nil) + (line-end-position)))) + (and (eq (or goal-column temporary-goal-column) most-positive-fixnum) + (memq this-command '(next-line previous-line)))) + (not (eolp)) + (not (memq this-command + '(evil-insert evil-beginning-of-line evil-first-non-blank))) + (forward-char)) + (unless (memq evil-state '(replace insert)) + (evil-change-state state)) + (when (eq 'insert evil-state) + (remove-hook 'pre-command-hook #'evil-repeat-pre-hook) + (remove-hook 'post-command-hook #'evil-repeat-post-hook) + (add-hook 'evil-insert-state-exit-hook #'evil--restore-repeat-hooks)) + (setq evil-execute-normal-keys nil)))) (setq evil-insert-count nil evil--execute-normal-return-state evil-state evil--execute-normal-eol-pos (when (eolp) (point-marker)) diff --git a/evil-common.el b/evil-common.el index 59938ebe..16ab56f9 100644 --- a/evil-common.el +++ b/evil-common.el @@ -48,30 +48,40 @@ (if (fboundp 'gui-set-selection) 'gui-set-selection 'x-set-selection)) ;; macro helper -(eval-and-compile - (defun evil-unquote (exp) - "Return EXP unquoted." - (while (eq (car-safe exp) 'quote) - (setq exp (cadr exp))) - exp)) +(defun evil--with-delay (cond-fun body-fun hook &optional append local name) + (if (and cond-fun (funcall cond-fun)) + (funcall body-fun) + (let* ((name (or name (format "evil-delay-in-%s" hook))) + (fun (make-symbol name))) + (fset fun (lambda (&rest _) + (when (or (null cond-fun) (funcall cond-fun)) + (remove-hook hook fun local) + (funcall body-fun)))) + (put fun 'permanent-local-hook t) + (add-hook hook fun append local)))) + +(defmacro evil-with-delay (condition hook &rest body) + "Execute BODY when CONDITION becomes true, checking with HOOK. +HOOK can be a simple symbol or of the form (HOOK APPEND LOCAL NAME) +where: +NAME specifies the name of the entry added to HOOK. +If APPEND is non-nil, the entry is appended to the hook. +If LOCAL is non-nil, the buffer-local value of HOOK is modified." + (declare (debug (form sexp body)) (indent 2)) + (unless (consp hook) (setq hook (list hook))) + ;; FIXME: Now the compiler doesn't know that `body' is only run if `condition' + ;; is true, so we get spurious warnings! :-( + `(evil--with-delay ,(when condition `(lambda () ,condition)) + (lambda () ,@body) + ,@(mapcar #'macroexp-quote hook))) (defun evil-delay (condition form hook &optional append local name) "Execute FORM when CONDITION becomes true, checking with HOOK. -NAME specifies the name of the entry added to HOOK. If APPEND is -non-nil, the entry is appended to the hook. If LOCAL is non-nil, +NAME specifies the name of the entry added to HOOK. If APPEND is +non-nil, the entry is appended to the hook. If LOCAL is non-nil, the buffer-local value of HOOK is modified." - (if (and (not (booleanp condition)) (eval condition)) - (eval form) - (let* ((name (or name (format "evil-delay-form-in-%s" hook))) - (fun (make-symbol name)) - (condition (or condition t))) - (fset fun `(lambda (&rest args) - (when ,condition - (remove-hook ',hook #',fun ',local) - ,form))) - (put fun 'permanent-local-hook t) - (add-hook hook fun append local)))) -(put 'evil-delay 'lisp-indent-function 2) + (declare (obsolete evil-with-delay "1.15.0") (indent 2)) + (eval `(evil-with-delay ,condition (,hook ,append ,local ,name) ,form) t)) ;;; List functions diff --git a/evil-core.el b/evil-core.el index bb170ae5..1b0dcc73 100644 --- a/evil-core.el +++ b/evil-core.el @@ -971,21 +971,27 @@ The symbol `local' may also be used, which corresponds to using `global' or `local', it is assumed to be the name of a minor mode, in which case `evil-define-minor-mode-key' is used." (declare (indent defun)) - (cond ((member keymap '('global 'local)) - `(evil-define-key* ,state ,keymap ,key ,def ,@bindings)) - ((eq (car-safe keymap) 'quote) - `(evil-define-minor-mode-key ,state ,keymap ,key ,def ,@bindings)) - (t - `(evil-delay ',(if (symbolp keymap) - `(and (boundp ',keymap) (keymapp ,keymap)) - `(keymapp ,keymap)) - '(condition-case-unless-debug err - (evil-define-key* ,state ,keymap ,key ,def ,@bindings) - (error (message "error in evil-define-key: %s" - (error-message-string err)))) - 'after-load-functions t nil - (format "evil-define-key-in-%s" - ',(if (symbolp keymap) keymap 'keymap)))))) + (cond + ((member keymap '('global 'local)) + `(evil-define-key* ,state ,keymap ,key ,def ,@bindings)) + ((eq (car-safe keymap) 'quote) + `(evil-define-minor-mode-key ,state ,keymap ,key ,def ,@bindings)) + (t `(evil-with-delay ,(if (symbolp keymap) + ;; BEWARE: Can't work for lexically scoped vars + `(and (boundp ',keymap) (keymapp ,keymap)) + `(keymapp ,keymap)) + (after-load-functions t nil + ,(format "evil-define-key-in-%s" + (if (symbolp keymap) keymap + 'keymap))) + ;; Sadly, the compiler doesn't understand `evil-with-delay's + ;; code well enough to figure out that the keymap var is + ;; necessarily bound since we just tested `boundp'. + ,(when (symbolp keymap) `(defvar ,keymap)) + (condition-case-unless-debug err + (evil-define-key* ,state ,keymap ,key ,def ,@bindings) + (error (message "error in evil-define-key: %s" + (error-message-string err)))))))) (defalias 'evil-declare-key #'evil-define-key) (defun evil-define-key* (state keymap key def &rest bindings) @@ -1028,7 +1034,7 @@ The use is nearly identical to `evil-define-key' with the exception that this is a function and not a macro (and so will not be expanded when compiled which can have unintended consequences). `evil-define-key*' also does not defer any -bindings like `evil-define-key' does using `evil-delay'. This +bindings like `evil-define-key' does using `evil-with-delay'. This allows errors in the bindings to be caught immediately, and makes its behavior more predictable." (declare (indent defun)) diff --git a/evil-states.el b/evil-states.el index 450ad2b5..c15b963d 100644 --- a/evil-states.el +++ b/evil-states.el @@ -381,17 +381,16 @@ otherwise exit Visual state." (defun evil-visual-activate-hook (&optional _command) "Enable Visual state if the region is activated." (unless (evil-visual-state-p) - (evil-delay nil - ;; the activation may only be momentary, so re-check - ;; in `post-command-hook' before entering Visual state - '(unless (or (evil-visual-state-p) - (evil-insert-state-p) - (evil-emacs-state-p)) - (when (and (region-active-p) - (not deactivate-mark)) - (evil-visual-state))) - 'post-command-hook nil t - "evil-activate-visual-state"))) + (evil-with-delay nil + (post-command-hook nil t "evil-activate-visual-state") + ;; the activation may only be momentary, so re-check + ;; in `post-command-hook' before entering Visual state + (unless (or (evil-visual-state-p) + (evil-insert-state-p) + (evil-emacs-state-p)) + (when (and (region-active-p) + (not deactivate-mark)) + (evil-visual-state)))))) (put 'evil-visual-activate-hook 'permanent-local-hook t) (defun evil-visual-deactivate-hook (&optional command)