Skip to content

Commit

Permalink
(evil-with-delay): New macro, extracted from evil-delay
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
monnier authored and tomdl89 committed Aug 20, 2023
1 parent 46ab271 commit 44add36
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 80 deletions.
68 changes: 35 additions & 33 deletions evil-commands.el
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
50 changes: 30 additions & 20 deletions evil-common.el
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
38 changes: 22 additions & 16 deletions evil-core.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
21 changes: 10 additions & 11 deletions evil-states.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 44add36

Please sign in to comment.