branch: elpa/evil commit b7b4961a14cd1a51e9a10564fd6c741567d39891 Author: Tom Dalziel <tom...@hotmail.com> Commit: Tom Dalziel <tom...@hotmail.com>
Urgent revert of recent commits while evil is broken Some of these commits can be almost immediately restored, once the time is found to do so. Revert "Fix evil-with-delay with dynamic binding" This reverts commit 1e9b2434264529fe0dd964b68fe89236a4abeac3. Revert "Some cleanups" This reverts commit 0cbd61f2de104fab16602d0418605cd0513b16f3. Revert "Misc minor changes" This reverts commit b291039b0c6ffc3b2f3c9f02b8ad2f0041127b12. Revert "Merge evil-with-delay condition and body lambdas" This reverts commit 1b56ffcc102b4c5f8b015e760b5f9cf5932622af. Revert "(evil-with-delay): New macro, extracted from `evil-delay`" This reverts commit 3d7faadf30016a8c20699a5fb1b5731b8a49dcd2. Revert "Make evil-search-wrap-ring-bell work with evil-search" This reverts commit 5e72cf5b6d57b785ea229236bb5c4638db2c9a05. Revert "Stop the '</'> and '[/'] marks from intertwining" This reverts commit 26db9441a13ebedb2481d7ada4c3b5e60ec22795. Revert "Remove redundant `:group` args" This reverts commit 6e30037fdc6a275d78d6b82d89bd8e47bcf4d4e3. Revert "Avoid eval in evil-test-buffer" This reverts commit 27d81ad406d2d3e07591b927357d2354ef5b5c65. Revert "Use lexical-binding everywhere" This reverts commit 44c7f301468c264a781be4ee8ae879fe1b457e60. --- README.md | 9 +- evil-command-window.el | 3 +- evil-commands.el | 370 +++++++++++++++++++++++++++---------------------- evil-common.el | 88 +++++++----- evil-core.el | 76 ++++++---- evil-ex.el | 52 ++++--- evil-jumps.el | 15 +- evil-macros.el | 4 +- evil-maps.el | 10 +- evil-search.el | 59 ++++---- evil-states.el | 21 +-- evil-test-helpers.el | 8 +- evil-tests.el | 122 ++++++++-------- evil-types.el | 2 +- evil-vars.el | 9 ++ evil.el | 4 +- 16 files changed, 473 insertions(+), 379 deletions(-) diff --git a/README.md b/README.md index 02215faf2a..198b18317c 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,10 @@ file. * Evil requires any of the following for `C-r`: * `undo-redo` from Emacs 28 - * The [undo-tree] package (available via GNU ELPA) - * The [undo-fu] package (available via MELPA and NonGNU ELPA) + * The [undo-tree](https://gitlab.com/tsc25/undo-tree) package + (available via GNU ELPA) + * The [undo-fu](https://gitlab.com/ideasman42/emacs-undo-fu) package + (available via MELPA) * For the motions `g;` `g,` and for the last-change-register `.`, Evil requires the [goto-chg.el](https://github.com/emacs-evil/goto-chg) @@ -78,6 +80,3 @@ Visit us on `irc.libera.chat #evil-mode`. See [CONTRIBUTING.md](https://github.com/emacs-evil/evil/blob/master/CONTRIBUTING.md) for guidelines for issues and pull requests. - -[undo-tree]: https://gitlab.com/tsc25/undo-tree -[undo-fu]: https://codeberg.org/ideasman42/emacs-undo-fu diff --git a/evil-command-window.el b/evil-command-window.el index c364e41868..68068a8384 100644 --- a/evil-command-window.el +++ b/evil-command-window.el @@ -44,7 +44,8 @@ (define-derived-mode evil-command-window-mode fundamental-mode "Evil-cmd" "Major mode for the Evil command line window." (auto-fill-mode 0) - (add-hook 'after-change-functions #'evil-command-window-draw-prefix nil t)) + (setq-local after-change-functions + (cons #'evil-command-window-draw-prefix after-change-functions))) (defun evil-command-window (history cmd-key execute-fn) "Open a command line window for HISTORY with CMD-KEY and EXECUTE-FN. diff --git a/evil-commands.el b/evil-commands.el index 7dfc1e0ae8..45f6b596c0 100644 --- a/evil-commands.el +++ b/evil-commands.el @@ -37,6 +37,7 @@ (require 'cl-lib) (require 'reveal) +(declare-function flyspell-overlay-p "flyspell") (declare-function imenu--in-alist "imenu") ;;; Motions @@ -214,9 +215,10 @@ move COUNT - 1 screen lines downward first." "Move the cursor to COUNT % of the width of the current line. If no COUNT is given, default to 50%." :type exclusive - (let ((line-length (- (line-end-position) (line-beginning-position) + (let ((line-length (- (line-end-position) + (line-beginning-position) (if evil-move-beyond-eol -1 0)))) - (move-to-column (truncate (* line-length (or count 50)) 100)))) + (move-to-column (truncate (* line-length (/ (or count 50) 100.0)))))) (evil-define-motion evil-first-non-blank () "Move the cursor to the first non-blank character of the current line." @@ -227,9 +229,13 @@ If no COUNT is given, default to 50%." "Move the cursor to the last non-blank character of the current line. If COUNT is given, move COUNT - 1 lines downward first." :type inclusive - (evil-move-end-of-line count) - (skip-chars-backward " \t") - (unless (bolp) (backward-char))) + (goto-char + (save-excursion + (evil-move-beginning-of-line count) + (if (re-search-forward "[ \t]*$") + (max (line-beginning-position) + (1- (match-beginning 0))) + (line-beginning-position))))) (evil-define-motion evil-first-non-blank-of-visual-line () "Move the cursor to the first non blank character @@ -492,28 +498,30 @@ and jump to the corresponding one." (t (let* ((open (point-max)) (close (point-max)) - (open-pair (ignore-errors - (save-excursion - ;; consider the character right before eol given that - ;; point may be placed there, e.g. in visual state - (when (and (eolp) (not (bolp))) - (backward-char)) - (setq open (1- (scan-lists (point) 1 -1))) - (when (< open (line-end-position)) - (goto-char open) - (forward-list) - (1- (point)))))) - (close-pair (ignore-errors - (save-excursion - ;; consider the character right before eol given that - ;; point may be placed there, e.g. in visual state - (when (and (eolp) (not (bolp))) - (backward-char)) - (setq close (1- (scan-lists (point) 1 1))) - (when (< close (line-end-position)) - (goto-char (1+ close)) - (backward-list) - (point)))))) + (open-pair (condition-case nil + (save-excursion + ;; consider the character right before eol given that + ;; point may be placed there, e.g. in visual state + (when (and (eolp) (not (bolp))) + (backward-char)) + (setq open (1- (scan-lists (point) 1 -1))) + (when (< open (line-end-position)) + (goto-char open) + (forward-list) + (1- (point)))) + (error nil))) + (close-pair (condition-case nil + (save-excursion + ;; consider the character right before eol given that + ;; point may be placed there, e.g. in visual state + (when (and (eolp) (not (bolp))) + (backward-char)) + (setq close (1- (scan-lists (point) 1 1))) + (when (< close (line-end-position)) + (goto-char (1+ close)) + (backward-list) + (point))) + (error nil)))) (cond ((not (or open-pair close-pair)) ;; nothing found, check if we are inside a string @@ -540,7 +548,6 @@ and jump to the corresponding one." ((< open close) (goto-char open-pair)) (t (goto-char close-pair))))))) -(declare-function flyspell-overlay-p "flyspell") (evil-define-motion evil-next-flyspell-error (count) "Go to the COUNT'th spelling mistake after point." :jump t @@ -601,16 +608,27 @@ and jump to the corresponding one." (defun evil--next-mark (forwardp) "Move to next lowercase mark. -Move forward if FORWARDP is non-nil, and backward otherwise. Loop back -to the beginning of buffer if the end is reached." - (let* ((pos (if forwardp (1+ (point)) (point))) - (centered-markers - (cl-sort - (or (evil--lowercase-markers) (user-error "No marks in this buffer")) - (if forwardp #'< #'>) - :key (lambda (x) (+ (if (< (cdr x) pos) 0 most-negative-fixnum) - (cdr x)))))) - (goto-char (cdar centered-markers)))) +Move forward if FORWARDP is truthy or backward if falsey. +Loop back to the top of buffer if the end is reached." + (let ((pos (point)) + (sorted-markers (sort (evil--lowercase-markers) + (lambda (a b) (< (cdr a) (cdr b)))))) + (cond + ((null sorted-markers) + (user-error "No marks in this buffer")) + (forwardp + (let ((next-marker (cl-some (lambda (x) (and (< pos (cdr x)) (cdr x))) + sorted-markers))) + (if next-marker + (goto-char (marker-position next-marker)) + (goto-char (marker-position (cdar sorted-markers)))))) + (t + (let* ((descending-markers (reverse sorted-markers)) + (prev-marker (cl-some (lambda (x) (and (> pos (cdr x)) (cdr x))) + descending-markers))) + (if prev-marker + (goto-char (marker-position prev-marker)) + (goto-char (marker-position (cdar descending-markers))))))))) (evil-define-motion evil-next-mark (count) "Go to COUNT next lowercase mark." @@ -627,11 +645,12 @@ to the beginning of buffer if the end is reached." :repeat nil :type exclusive :jump t - (unless (evil--lowercase-markers) (user-error "No marks in this buffer")) - (dotimes (_ (or count 1)) - (move-end-of-line nil) - (evil--next-mark t)) - (evil-first-non-blank)) + (if (evil--lowercase-markers) + (dotimes (_ (or count 1)) + (evil-end-of-line) + (evil--next-mark t) + (evil-first-non-blank)) + (user-error "No marks in this buffer"))) (evil-define-motion evil-previous-mark (count) "Go to COUNT previous lowercase mark." @@ -648,15 +667,15 @@ to the beginning of buffer if the end is reached." :repeat nil :type exclusive :jump t - (unless (evil--lowercase-markers) (user-error "No marks in this buffer")) - (dotimes (_ (or count 1)) - (move-beginning-of-line nil) - (evil--next-mark nil)) - (evil-first-non-blank)) + (if (evil--lowercase-markers) + (dotimes (_ (or count 1)) + (evil-beginning-of-line) + (evil--next-mark nil) + (evil-first-non-blank)) + (user-error "No marks in this buffer"))) (evil-define-command evil-set-col-0-mark (beg end mark) - "Set MARK at column 0 of line of END. -Default is cursor line." + "Set MARK at column 0 of line of END. Default is cursor line." (interactive "<r><a>") (if (< 1 (length mark)) (user-error "Trailing characters") @@ -667,8 +686,7 @@ Default is cursor line." (evil-define-motion evil-find-char (count char) "Move to the next COUNT'th occurrence of CHAR. -Movement is restricted to the current line unless `evil-cross-lines' -is non-nil." +Movement is restricted to the current line unless `evil-cross-lines' is non-nil." :type inclusive (interactive "<c><C>") (setq count (or count 1)) @@ -707,11 +725,12 @@ is non-nil." :type inclusive (interactive "<c><C>") (unwind-protect - (evil-find-char count char) - (setcar evil-last-find #'evil-find-char-to)) - (if (> (or count 1) 0) - (backward-char) - (forward-char))) + (progn + (evil-find-char count char) + (if (> (or count 1) 0) + (backward-char) + (forward-char))) + (setcar evil-last-find #'evil-find-char-to))) (evil-define-motion evil-find-char-to-backward (count char) "Move before the previous COUNT'th occurrence of CHAR." @@ -723,22 +742,26 @@ is non-nil." "Repeat the last find COUNT times." :type inclusive (setq count (or count 1)) - (cl-destructuring-bind (cmd char fwd) - (or evil-last-find (user-error "No previous search")) - ;; ensure count is non-negative - (when (< count 0) - (setq count (- count) - fwd (not fwd))) - ;; skip next character when repeating t or T - (and (eq cmd #'evil-find-char-to) - evil-repeat-find-to-skip-next - (= count 1) - (eql (if fwd (char-after (1+ (point))) (char-before)) char) - (setq count (1+ count))) - (let (evil-last-find) - (funcall cmd (if fwd count (- count)) char) - (unless (nth 2 evil-last-find) - (setq evil-this-type 'exclusive))))) + (if evil-last-find + (let ((cmd (car evil-last-find)) + (char (nth 1 evil-last-find)) + (fwd (nth 2 evil-last-find)) + evil-last-find) + ;; ensure count is non-negative + (when (< count 0) + (setq count (- count) + fwd (not fwd))) + ;; skip next character when repeating t or T + (and (eq cmd #'evil-find-char-to) + evil-repeat-find-to-skip-next + (= count 1) + (or (and fwd (= (char-after (1+ (point))) char)) + (and (not fwd) (= (char-before) char))) + (setq count (1+ count))) + (funcall cmd (if fwd count (- count)) char) + (unless (nth 2 evil-last-find) + (setq evil-this-type 'exclusive))) + (user-error "No previous search"))) (evil-define-motion evil-repeat-find-char-reverse (count) "Repeat the last find COUNT times in the opposite direction." @@ -1984,7 +2007,9 @@ If a `#' is included before the mark args, the lines are numbered." :move-point nil :type line (save-excursion - (ignore-errors (fill-region beg end)))) + (condition-case nil + (fill-region beg end) + (error nil)))) (evil-define-operator evil-fill-and-move (beg end) "Fill text and move point to the end of the filled region." @@ -1992,10 +2017,12 @@ If a `#' is included before the mark args, the lines are numbered." :type line (let ((marker (make-marker))) (move-marker marker (1- end)) - (ignore-errors - (fill-region beg end) - (goto-char marker) - (evil-first-non-blank)))) + (condition-case nil + (progn + (fill-region beg end) + (goto-char marker) + (evil-first-non-blank)) + (error nil)))) (evil-define-operator evil-indent (beg end) "Indent text." @@ -2380,8 +2407,10 @@ leave the cursor just after the new text." (when evil-kill-on-visual-paste (current-kill -1)) ;; Ensure that gv can restore visually pasted area... - (set-marker evil-visual-point (evil-get-marker (if (< dir 0) ?\[ ?\]) t)) - (set-marker evil-visual-mark (evil-get-marker (if (< dir 0) ?\] ?\[) t)) + (setq evil-visual-previous-mark evil-visual-mark + evil-visual-mark (evil-get-marker (if (< 0 dir) ?\[ ?\]) t) + evil-visual-previous-point evil-visual-point + evil-visual-point (evil-get-marker (if (< 0 dir) ?\] ?\[) t)) ;; mark the last paste as visual-paste (setq evil-last-paste (list (nth 0 evil-last-paste) @@ -2394,23 +2423,26 @@ leave the cursor just after the new text." (defun evil-paste-from-register (register) "Paste from REGISTER." (interactive - (let ((ov (make-overlay (point) (+ (point) (if (evil-replace-state-p) 1 0))))) + (let* ((opoint (point)) + (overlay (make-overlay opoint (+ opoint (if (evil-replace-state-p) 1 0))))) (unwind-protect (progn - (overlay-put ov 'invisible t) - (overlay-put ov 'after-string - #("\"" 0 1 (face minibuffer-prompt cursor 1))) + (overlay-put overlay 'invisible t) + (overlay-put overlay 'after-string (propertize "\"" + 'face 'minibuffer-prompt + 'cursor 1)) (list (or evil-this-register (read-char)))) - (delete-overlay ov)))) + (delete-overlay overlay)))) (let ((opoint (point)) - evil-move-cursor-back) + (evil-move-cursor-back nil) + reg-length chars-to-delete) (evil-paste-before nil register t) (when (evil-replace-state-p) - (let* ((reg-length (- (point) opoint)) - (chars-to-delete (min (- (line-end-position) (point)) reg-length))) - ;; TODO: handle multi-line paste backspacing - (evil-update-replace-alist (point) reg-length chars-to-delete chars-to-delete) - (delete-char chars-to-delete))))) + (setq reg-length (- (point) opoint) + chars-to-delete (min (- (line-end-position) (point)) reg-length)) + ;; TODO: handle multi-line paste backspacing + (evil-update-replace-alist (point) reg-length chars-to-delete chars-to-delete) + (delete-char chars-to-delete)))) (defun evil-paste-last-insertion () "Paste last insertion." @@ -2466,8 +2498,10 @@ will be opened instead." ((eq register ?\C-g) (keyboard-quit)) ((and evil-this-macro defining-kbd-macro) - (setq evil-macro-buffer nil - last-macro (ignore-errors (evil-end-and-return-macro))) + (setq evil-macro-buffer nil) + (condition-case nil + (setq last-macro (evil-end-and-return-macro)) + (error nil)) (when last-macro (evil-set-register evil-this-macro last-macro)) (setq evil-this-macro nil)) @@ -2739,23 +2773,26 @@ switch to insert state." (defun evil-quoted-insert (count) "Like `quoted-insert' but delete COUNT chars forward in replace state. -Adds a \"^\" overlay as an input prompt." +Adds a `^' overlay as an input prompt." (interactive "p") (let* ((opoint (point)) - chars-to-delete - (ov (if (not (evil-replace-state-p)) - (make-overlay opoint opoint) - (setq chars-to-delete (min (- (line-end-position) opoint) count)) - (evil-update-replace-alist opoint count chars-to-delete) - (make-overlay opoint (+ chars-to-delete opoint))))) + chars-to-delete insert-prompt) (unwind-protect (progn - (overlay-put ov 'invisible t) - (overlay-put ov 'after-string #("^" 0 1 (face escape-glyph cursor 1))) - (let (overwrite-mode) ; Force `read-quoted-char' + (if (evil-replace-state-p) + (progn + (setq chars-to-delete (min (- (line-end-position) opoint) count) + insert-prompt (make-overlay opoint (+ chars-to-delete opoint))) + (evil-update-replace-alist opoint count chars-to-delete)) + (setq insert-prompt (make-overlay opoint opoint))) + (overlay-put insert-prompt 'invisible t) + (overlay-put insert-prompt 'after-string (propertize "^" + 'face 'escape-glyph + 'cursor 1)) + (let (overwrite-mode) ;; Force `read-quoted-char' (quoted-insert count)) - (when chars-to-delete (delete-char chars-to-delete))) - (delete-overlay ov)))) + (when (evil-replace-state-p) (delete-char chars-to-delete))) + (when insert-prompt (delete-overlay insert-prompt))))) (evil-define-command evil-open-above (count) "Insert a new line above point and switch to Insert state. @@ -3157,9 +3194,10 @@ The search is unbounded, i.e., the pattern is not wrapped in (let ((identifier (save-excursion (goto-char position) (xref-backend-identifier-at-point (xref-find-backend))))) - (condition-case nil - (progn (xref-find-definitions identifier) - t) + (condition-case () + (progn + (xref-find-definitions identifier) + t) (user-error nil))))) (defun evil-goto-definition-search (string _position) @@ -3364,7 +3402,8 @@ files." (evil-define-command evil-goto-error (count) "Go to error number COUNT. -If no COUNT is supplied, move to the current error. + +If no COUNT supplied, move to the current error. Acts like `first-error' other than when given no counts, goes to the current error instead of the first, like in Vim's :cc @@ -3424,12 +3463,17 @@ for the last window in each frame." ;; if the buffer which was initiated by emacsclient, ;; call `server-edit' from server.el to avoid ;; "Buffer still has clients" message - (if (and (bound-and-true-p server-buffer-clients) - (fboundp 'server-edit)) + (if (and (fboundp 'server-edit) + (boundp 'server-buffer-clients) + server-buffer-clients) (server-edit) (kill-buffer nil)) ;; close all windows that showed this buffer - (dolist (w wins) (ignore-errors (delete-window w)))))) + (mapc #'(lambda (w) + (condition-case nil + (delete-window w) + (error nil))) + wins)))) (evil-define-command evil-quit (&optional force) "Close the current window, current frame, current tab, Emacs. @@ -3440,9 +3484,10 @@ is closed." (condition-case nil (delete-window) (error - (if (and (bound-and-true-p server-buffer-clients) + (if (and (boundp 'server-buffer-clients) (fboundp 'server-edit) - (fboundp 'server-buffer-done)) + (fboundp 'server-buffer-done) + server-buffer-clients) (if force (server-buffer-done (current-buffer)) (server-edit)) @@ -3476,7 +3521,10 @@ The FORCE argument is only there for compatibility and is ignored. This function fails with an error if Emacs is run in server mode." :repeat nil (interactive "<!>") - (if (bound-and-true-p server-buffer-clients) + (if (and (boundp 'server-buffer-clients) + (fboundp 'server-edit) + (fboundp 'server-buffer-done) + server-buffer-clients) (user-error "Cannot exit client process with error code.") (kill-emacs 1))) @@ -4223,7 +4271,7 @@ Use `evil-flush-lines' if INVERT is nil, or `evil-keep-lines' if not." (let ((evil--ex-global-active-p t)) (dolist (marker markers) (goto-char marker) - (eval command-form t)))) + (eval command-form)))) ;; ensure that all markers are deleted afterwards, ;; even in the event of failure (dolist (marker markers) @@ -4405,11 +4453,8 @@ The \"!\" argument means to sort in reverse order." "Toggle side windows, evaluate BODY, restore side windows." (declare (indent defun) (debug (&rest form))) (let ((sides (make-symbol "sidesvar"))) - `(let ((,sides (and (fboundp 'window-toggle-side-windows) + `(let ((,sides (and (functionp 'window-toggle-side-windows) (window-with-parameter 'window-side)))) - ;; The compiler doesn't understand that all uses are protected - ;; by `fboundp' :-( - (declare-function window-toggle-side-windows "window") (when ,sides (window-toggle-side-windows)) (unwind-protect @@ -4479,14 +4524,17 @@ the deleted window's parent window are rebalanced." (let ((p (window-parent))) ;; If tabs are enabled and this is the only visible window, then attempt to ;; close this tab. - (if (and (bound-and-true-p tab-bar-mode) - (null p)) + (if (and (boundp 'tab-bar-mode) + tab-bar-mode + (not p)) (tab-close) - (delete-window) - (when evil-auto-balance-windows - ;; balance-windows raises an error if the parent does not have - ;; any further children (then rebalancing is not necessary anyway) - (ignore-errors (balance-windows p)))))) + (delete-window)) + (when evil-auto-balance-windows + ;; balance-windows raises an error if the parent does not have + ;; any further children (then rebalancing is not necessary anyway) + (condition-case nil + (balance-windows p) + (error))))) (evil-define-command evil-window-split (&optional count file) "Split the current window horizontally, COUNT lines height, @@ -5070,41 +5118,39 @@ Restore the disabled repeat hooks on insert-state exit." (defun evil-execute-in-normal-state () "Execute the next command in Normal state." (interactive) - (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)))) + (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) (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 9504cda27f..e423a1c4f0 100644 --- a/evil-common.el +++ b/evil-common.el @@ -35,6 +35,7 @@ (declare-function evil-visual-restore "evil-states") (declare-function evil-motion-state "evil-states") (declare-function evil-replace-state-p "evil-states") +(declare-function evil-ex-p "evil-ex") (declare-function evil-set-jump "evil-jumps") ;;; Compatibility with different Emacs versions @@ -46,38 +47,31 @@ (defalias 'evil-set-selection (if (fboundp 'gui-set-selection) 'gui-set-selection 'x-set-selection)) -(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)) - (cl-destructuring-bind (hook-sym &optional append local name) - (mapcar #'macroexp-quote (if (consp hook) hook (list hook))) - (macroexp-let2* nil - ((fun-name `(make-symbol - ,(or name (format "evil-delay-in-%s" hook-sym)))) - (fun `(apply-partially - (lambda (name &rest _) - (when ,(or condition t) - (remove-hook ,hook-sym name ,local) - ,@body - t)) - ,fun-name))) - `(unless ,(and condition `(funcall ,fun)) - (progn (fset ,fun-name ,fun) - ,@(when local `((put ,fun-name 'permanent-local-hook t))) - (add-hook ,hook-sym ,fun-name ,append ,local)))))) +;; 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-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." - (declare (obsolete evil-with-delay "1.15.0") (indent 2)) - (eval `(evil-with-delay ,condition (,hook ,append ,local ,name) ,form) t)) + (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) ;;; List functions @@ -763,14 +757,15 @@ cursor type is either `evil-force-cursor' or the current state." (defmacro evil-save-cursor (&rest body) "Save the current cursor; execute BODY; restore the cursor." - (declare (indent defun) (debug t) (obsolete nil "1.15.0")) + (declare (indent defun) + (debug t)) `(let ((cursor cursor-type) (color (frame-parameter (selected-frame) 'cursor-color)) (inhibit-quit t)) (unwind-protect (progn ,@body) - (setq cursor-type cursor) - (evil-set-cursor-color color)))) + (evil-set-cursor cursor) + (evil-set-cursor color)))) (defun evil-echo (string &rest args) "Display an unlogged message in the echo area. @@ -798,12 +793,15 @@ Does not restore if `evil-write-echo-area' is non-nil." (defmacro evil-save-echo-area (&rest body) "Save the echo area; execute BODY; restore the echo area. Intermittent messages are not logged in the *Messages* buffer." - (declare (indent defun) (debug t)) + (declare (indent defun) + (debug t)) `(let ((inhibit-quit t) - evil-echo-area-message evil-write-echo-area) - (evil-echo-area-save) + evil-echo-area-message + evil-write-echo-area) (unwind-protect - (progn ,@body) + (progn + (evil-echo-area-save) + ,@body) (evil-echo-area-restore)))) (defmacro evil-without-display (&rest body) @@ -1890,7 +1888,7 @@ If INPUT starts with a number, +, -, or . use `calc-eval' instead." (result (if calcable-p (let ((calc-multiplication-has-precedence nil)) (calc-eval input)) - (eval (car (read-from-string input)) t)))) + (eval (car (read-from-string input)))))) (cond ((stringp result) result) ((or (numberp result) (symbolp result)) @@ -2162,6 +2160,21 @@ The earlier settings of Transient Mark mode are stored in (funcall var (if val 1 -1)) (setq var val)))))) +(defun evil-save-mark () + "Save the current mark, including whether it is transient. +See also `evil-restore-mark'." + (unless evil-visual-previous-mark + (setq evil-visual-previous-mark (mark t)) + (evil-save-transient-mark-mode))) + +(defun evil-restore-mark () + "Restore the mark, including whether it was transient. +See also `evil-save-mark'." + (when evil-visual-previous-mark + (evil-restore-transient-mark-mode) + (evil-move-mark evil-visual-previous-mark) + (setq evil-visual-previous-mark nil))) + ;; In theory, an active region implies Transient Mark mode, and ;; disabling Transient Mark mode implies deactivating the region. ;; In practice, Emacs never clears `mark-active' except in Transient @@ -2521,6 +2534,9 @@ is negative this is a more recent kill." (unless evil-last-paste (user-error "Previous paste command used a register")) (evil-undo-pop) + (when (eq last-command 'evil-visual-paste) + (evil-swap evil-visual-previous-mark evil-visual-mark) + (evil-swap evil-visual-previous-point evil-visual-point)) (goto-char (nth 2 evil-last-paste)) (setq this-command (nth 0 evil-last-paste)) ;; use temporary kill-ring, so the paste cannot modify it diff --git a/evil-core.el b/evil-core.el index 23937e7d30..bb170ae5d5 100644 --- a/evil-core.el +++ b/evil-core.el @@ -127,7 +127,7 @@ (add-hook 'input-method-activate-hook #'evil-activate-input-method t t) (add-hook 'input-method-deactivate-hook #'evil-deactivate-input-method t t) (add-hook 'activate-mark-hook 'evil-visual-activate-hook nil t) - ;; FIXME: Add these hooks buffer-locally and remove when disabling + ;; FIXME: Add these hooks buffer-locally (add-hook 'pre-command-hook 'evil-repeat-pre-hook) (add-hook 'post-command-hook 'evil-repeat-post-hook)) (evil-refresh-mode-line) @@ -225,10 +225,15 @@ Restore the previous state afterwards." (evil-change-state ',state) ,@body))) -(defun evil-initialize-state () - "Set up the initial state for the current buffer. +(defun evil-initialize-state (&optional state buffer) + "Set up the initial state for BUFFER. +BUFFER defaults to the current buffer. +Uses STATE if specified, or calls `evil-initial-state-for-buffer'. See also `evil-set-initial-state'." - (evil-change-state (evil-initial-state-for-buffer))) + (with-current-buffer (or buffer (current-buffer)) + (evil-change-state + (or state (evil-initial-state-for-buffer buffer))))) +(put 'evil-initialize-state 'permanent-local-hook t) (defun evil-initial-state-for-buffer-name (&optional name default) "Return the initial Evil state to use for a buffer with name NAME. @@ -249,12 +254,17 @@ Matches the name against the regular expressions in (defun evil-initial-state-for-buffer (&optional buffer) "Return the initial Evil state to use for BUFFER. -BUFFER defaults to the current buffer. See also `evil-initial-state'." +BUFFER defaults to the current buffer. Returns DEFAULT +if no initial state is associated with BUFFER. +See also `evil-initial-state'." (with-current-buffer (or buffer (current-buffer)) (or (evil-initial-state-for-buffer-name) - (cl-loop for (mode) in minor-mode-map-alist - when (and (boundp mode) (symbol-value mode)) - thereis (evil-initial-state mode)) + (catch 'done + (dolist (mode minor-mode-map-alist) + (setq mode (car mode)) + (and (boundp mode) (symbol-value mode) + (setq mode (evil-initial-state mode)) + (throw 'done mode)))) (evil-initial-state major-mode nil t) evil-default-state))) @@ -330,10 +340,11 @@ then this function does nothing." ;; otherwise, though, so advise this function to initialize Evil. (defadvice set-window-buffer (before evil) "Initialize Evil in the displayed buffer." - (when (and evil-mode (get-buffer (ad-get-arg 1))) - (with-current-buffer (ad-get-arg 1) - (unless evil-local-mode - (save-match-data (evil-initialize)))))) + (when evil-mode + (when (get-buffer (ad-get-arg 1)) + (with-current-buffer (ad-get-arg 1) + (unless evil-local-mode + (save-match-data (evil-initialize))))))) ;; Refresh cursor color. ;; Cursor color can only be set for each frame but not for each buffer. @@ -441,7 +452,7 @@ This allows input methods to be used in normal-state." "Initialize a buffer-local value for local keymaps as necessary. The initial value is that of `make-sparse-keymap'." (dolist (entry evil-local-keymaps-alist) - (let ((map (cdr entry))) + (let ((map (cdr entry))) (unless (and (keymapp (symbol-value map)) (local-variable-p map)) (set map (make-sparse-keymap)))))) @@ -802,6 +813,7 @@ If AUX is nil, create a new auxiliary keymap." (format "%s state" state))))) (define-key map (vector (intern (format "%s-state" state))) aux) aux) +(put 'evil-set-auxiliary-keymap 'lisp-indent-function 'defun) (defun evil-get-auxiliary-keymap (map state &optional create ignore-parent) "Get the auxiliary keymap for MAP in STATE. @@ -959,20 +971,21 @@ 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-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))) - (with-demoted-errors "Error in evil-define-key: %S" - (evil-define-key* ,state ,keymap ,key ,def ,@bindings)))))) + (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)))))) (defalias 'evil-declare-key #'evil-define-key) (defun evil-define-key* (state keymap key def &rest bindings) @@ -1015,7 +1028,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-with-delay'. This +bindings like `evil-define-key' does using `evil-delay'. This allows errors in the bindings to be caught immediately, and makes its behavior more predictable." (declare (indent defun)) @@ -1272,7 +1285,9 @@ If ARG is nil, don't display a message in the echo area.%s" name doc) (deactivate-input-method))) (unless evil-no-display (evil-refresh-cursor ',state) - (evil-refresh-mode-line ',state)) + (evil-refresh-mode-line ',state) + (when (called-interactively-p 'any) + (redisplay))) ,@body (run-hooks ',entry-hook) (when (and evil-echo-state @@ -1281,7 +1296,8 @@ If ARG is nil, don't display a message in the echo area.%s" name doc) (funcall ,message) (evil-echo "%s" ,message)))))))) - (evil-add-command-properties ',toggle :keep-visual t :suppress-operator t) + (evil-set-command-property ',toggle :keep-visual t) + (evil-set-command-property ',toggle :suppress-operator t) (evil-define-keymap ,keymap nil :mode ,mode diff --git a/evil-ex.el b/evil-ex.el index f91c1a1447..5ae3aa4225 100644 --- a/evil-ex.el +++ b/evil-ex.el @@ -44,6 +44,7 @@ (require 'evil-common) (require 'evil-states) +(require 'evil-types) (declare-function evil-goto-line "evil-commands") @@ -76,9 +77,10 @@ #'(evil-ex-char-marker-range $2 $4))) (line ((\? base) (\? offset) search (\? offset) - #'(save-excursion - (goto-line (evil-ex-line $1 $2)) - (evil-ex-line $3 $4))) + #'(let ((tmp (evil-ex-line $1 $2))) + (save-excursion + (goto-line tmp) + (evil-ex-line $3 $4)))) (base (\? offset) #'evil-ex-line) (nil offset #'evil-ex-line)) (base @@ -304,9 +306,6 @@ The following symbols have reserved meanings within a grammar: (defvar evil-ex-argument-types nil "Association list of argument handlers.") -(defvar evil-ex-commands nil - "Association list of command bindings and functions.") - (defvar evil-ex-reverse-range nil "Whether the current Ex range was entered reversed.") @@ -400,9 +399,8 @@ symbol, which defaults to `expression'." #'delete-backward-char #'abort-recursive-edit))) -(cl-defstruct (evil-ex-argument-handler (:type list) (:constructor nil) - (:copier nil) (:predicate nil)) - (runner nil :read-only t) (completer nil :read-only t)) +(define-obsolete-function-alias + 'evil-ex-elisp-completion-at-point #'elisp-completion-at-point "1.15.0") (defun evil-ex-setup () "Initialize Ex minibuffer. @@ -415,9 +413,10 @@ actions during Ex state." (defun evil-ex-teardown () "Deinitialize Ex minibuffer. Clean up everything set up by `evil-ex-setup'." - (let ((runner (evil-ex-argument-handler-runner - evil--ex-argument-handler))) - (when runner (funcall runner 'stop)))) + (when evil--ex-argument-handler + (let ((runner (evil-ex-argument-handler-runner + evil--ex-argument-handler))) + (when runner (funcall runner 'stop))))) (put 'evil-ex-teardown 'permanent-local-hook t) (defsubst evil--ex-bang-p (command) @@ -490,10 +489,13 @@ in case of incomplete or unknown commands." (defun evil--ex-remove-echo-overlay () "Remove echo overlay from Ex minibuffer." - (delete-overlay evil--ex-echo-overlay) - (setq evil--ex-echo-overlay nil) + (when evil--ex-echo-overlay + (delete-overlay evil--ex-echo-overlay) + (setq evil--ex-echo-overlay nil)) (remove-hook 'pre-command-hook #'evil--ex-remove-echo-overlay t)) +(define-obsolete-function-alias 'evil-ex-completion #'completion-at-point "1.15.0") + (cl-defun evil-ex-completion-at-point () "Function used for `completion-at-point-functions' in Ex state." (cl-flet ((fix-beg (b) (min (save-excursion @@ -524,6 +526,9 @@ in case of incomplete or unknown commands." (`((expression) (sexp . ,_)) (when (fboundp 'elisp-completion-at-point) (elisp-completion-at-point)))))) +(define-obsolete-function-alias + 'evil-ex-command-completion-at-point #'evil-ex-completion-at-point "1.15.0") + (defun evil-ex-completion-table () (let ((ex-cmds (cl-loop @@ -572,18 +577,10 @@ in case of incomplete or unknown commands." (p2 (eq (get-text-property 0 'face str2) 'evil-ex-commands))) (if (eq p1 p2) (string< str1 str2) p1))))) -(define-obsolete-function-alias 'evil-ex-completion #'completion-at-point "1.15.0") - -(define-obsolete-function-alias - 'evil-ex-command-completion-at-point #'evil-ex-completion-at-point "1.15.0") - (defalias 'evil-ex-argument-completion-at-point #'ignore) (make-obsolete 'evil-ex-argument-completion-at-point #'evil-ex-completion-at-point "1.15.0") -(define-obsolete-function-alias - 'evil-ex-elisp-completion-at-point #'elisp-completion-at-point "1.15.0") - (defun evil-ex-define-cmd (cmd function) "Bind the function FUNCTION to the command CMD." (if (string-match "\\[\\(.*\\)\\]" cmd) @@ -594,6 +591,15 @@ in case of incomplete or unknown commands." abbrev full)) (evil--add-to-alist evil-ex-commands cmd function))) +(defsubst evil-ex-make-argument-handler (runner completer) + (list runner completer)) + +(defun evil-ex-argument-handler-runner (arg-handler) + (car arg-handler)) + +(defun evil-ex-argument-handler-completer (arg-handler) + (cadr arg-handler)) + (defmacro evil-ex-define-argument-type (arg-type doc &rest body) "Define a new handler for argument-type ARG-TYPE. DOC is the documentation string. It is followed by a list of keywords @@ -829,7 +835,7 @@ This function interprets special file names like # and %." (defun evil-ex-last-visual-range () "Return a linewise range of the last visual selection." - (evil-range evil-visual-mark evil-visual-point 'line)) + (evil-line-expand evil-visual-mark evil-visual-point)) (defun evil-ex-marker (marker) "Return MARKER's line number in the current buffer. diff --git a/evil-jumps.el b/evil-jumps.el index 2fadb8b205..93bf4cbcb6 100644 --- a/evil-jumps.el +++ b/evil-jumps.el @@ -38,23 +38,28 @@ (defcustom evil-jumps-cross-buffers t "When non-nil, the jump commands can cross borders between buffers. Otherwise the jump commands act only within the current buffer." - :type 'boolean) + :type 'boolean + :group 'evil-jumps) (defcustom evil-jumps-max-length 100 "The maximum number of jumps to keep track of." - :type 'integer) + :type 'integer + :group 'evil-jumps) (defcustom evil-jumps-pre-jump-hook nil "Hooks to run just before jumping to a location in the jump list." - :type 'hook) + :type 'hook + :group 'evil-jumps) (defcustom evil-jumps-post-jump-hook nil "Hooks to run just after jumping to a location in the jump list." - :type 'hook) + :type 'hook + :group 'evil-jumps) (defcustom evil-jumps-ignored-file-patterns '("COMMIT_EDITMSG$" "TAGS$") "List of regexps to exclude file path from inclusion in the jump list." - :type '(repeat string)) + :type '(repeat string) + :group 'evil-jumps) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/evil-macros.el b/evil-macros.el index 1a60051e6d..90ae28bef0 100644 --- a/evil-macros.el +++ b/evil-macros.el @@ -746,10 +746,10 @@ via KEY-VALUE pairs. BODY should evaluate to a list of values. `(lambda ,args ,@(when doc `(,doc)) ,@body) - `',(macroexp-progn body)))) + (macroexp-progn body)))) `(eval-and-compile (evil--add-to-alist - evil-interactive-alist ,code (cons ,func ',properties)) + evil-interactive-alist ,code (cons ',func ',properties)) ,code))) ;;; Highlighting diff --git a/evil-maps.el b/evil-maps.el index fbf5af1695..678ebfd80d 100644 --- a/evil-maps.el +++ b/evil-maps.el @@ -25,14 +25,14 @@ ;; You should have received a copy of the GNU General Public License ;; along with Evil. If not, see <http://www.gnu.org/licenses/>. -;;; Code: - (require 'evil-states) (require 'evil-ex) (require 'evil-commands) (require 'evil-command-window) (require 'evil-common) +;;; Code: + ;;; Normal state (define-key evil-normal-state-map "a" 'evil-append) @@ -668,8 +668,10 @@ included in `evil-insert-state-bindings' by default." (define-key evil-read-key-map "\r" "\n") ;; command line window -(evil-define-key* '(normal insert) evil-command-window-mode-map - (kbd "RET") 'evil-command-window-execute) +(evil-define-key 'normal + evil-command-window-mode-map (kbd "RET") 'evil-command-window-execute) +(evil-define-key 'insert + evil-command-window-mode-map (kbd "RET") 'evil-command-window-execute) (provide 'evil-maps) diff --git a/evil-search.el b/evil-search.el index d4f870bcb6..ec292a1eb0 100644 --- a/evil-search.el +++ b/evil-search.el @@ -389,7 +389,9 @@ letter, otherwise it will be case-insensitive." ((string-match "\\(?:^\\|[^\\\\]\\)\\(?:\\\\\\\\\\)*\\\\\\([cC]\\)" re) (if (eq (aref (match-string 1 re) 0) ?c) 'insensitive 'sensitive)) ((eq default-case 'smart) - (if (isearch-no-upper-case-p re t) 'insensitive 'sensitive)) + (if (isearch-no-upper-case-p re t) + 'insensitive + 'sensitive)) (t default-case))) ;; a pattern @@ -397,15 +399,17 @@ letter, otherwise it will be case-insensitive." "Create a PATTERN for substitution with FLAGS. This function respects the values of `evil-ex-substitute-case' and `evil-ex-substitute-global'." - (evil-ex-make-pattern - regexp - (cond ((memq ?i flags) 'insensitive) - ((memq ?I flags) 'sensitive) - ((not evil-ex-substitute-case) evil-ex-search-case) - (t evil-ex-substitute-case)) - (if (memq ?g flags) - (not evil-ex-substitute-global) - evil-ex-substitute-global))) + (evil-ex-make-pattern regexp + (cond + ((memq ?i flags) 'insensitive) + ((memq ?I flags) 'sensitive) + ((not evil-ex-substitute-case) + evil-ex-search-case) + (t evil-ex-substitute-case)) + (or (and evil-ex-substitute-global + (not (memq ?g flags))) + (and (not evil-ex-substitute-global) + (memq ?g flags))))) (defun evil-ex-make-search-pattern (regexp) "Create a PATTERN for search. @@ -429,14 +433,15 @@ style regular expression and is not transformed." ;; possibly transform regular expression from vim-style to ;; Emacs-style. (if (and evil-ex-search-vim-style-regexp - (not (or (string-prefix-p "\\_<" regexp) - (string-suffix-p "\\_>" regexp)))) + (not (or (string-match-p "\\`\\\\_<" regexp) + (string-match-p "\\\\_>\\'" regexp)))) (setq re (evil-transform-vim-style-regexp re)) ;; Even for Emacs regular expressions we translate certain ;; whitespace sequences - (setq re (evil-transform-regexp re '((?t . "\t") - (?n . "\n") - (?r . "\r"))))) + (setq re (evil-transform-regexp re + '((?t . "\t") + (?n . "\n") + (?r . "\r"))))) (list re ignore-case whole-line))) (defun evil-ex-pattern-regex (pattern) @@ -711,20 +716,20 @@ This function does nothing if `evil-ex-search-interactive' or (evil-ex-hl-change 'evil-ex-search pattern))))) (defun evil-ex-search (&optional count) - "Search forward or backward COUNT times for the current Ex search pattern. -The search pattern is determined by `evil-ex-search-pattern', and the -direction by `evil-ex-search-direction'." + "Search forward or backward COUNT times for the current ex search pattern. +The search pattern is determined by `evil-ex-search-pattern' and +the direcion is determined by `evil-ex-search-direction'." (setq evil-ex-search-start-point (point) evil-ex-last-was-search t count (or count 1)) (let ((orig (point)) wrapped) (dotimes (_ (or count 1)) - (when (and (eq evil-ex-search-direction 'forward) (not (eobp))) - (forward-char) + (when (eq evil-ex-search-direction 'forward) + (unless (eobp) (forward-char)) ;; maybe skip end-of-line - (and (not evil-move-beyond-eol) (eolp) (not (eobp)) - (forward-char))) + (when (and (not evil-move-beyond-eol) (eolp) (not (eobp))) + (forward-char))) (let ((res (evil-ex-find-next nil nil (not evil-search-wrap)))) (cond ((not res) @@ -734,7 +739,6 @@ direction by `evil-ex-search-direction'." ((eq res 'wrapped) (setq wrapped t))))) (if wrapped (let (message-log-max) - (when evil-search-wrap-ring-bell (ding)) (message "Search wrapped"))) (goto-char (match-beginning 0)) (setq evil-ex-search-match-beg (match-beginning 0) @@ -1035,11 +1039,10 @@ current search result." evil-ex-search-match-end (match-end 0)) (evil-ex-search-goto-offset offset) (evil-push-search-history search-string (eq direction 'forward)) - (if evil-ex-search-incremental - (unless evil-ex-search-persistent-highlight - (evil-ex-delete-hl 'evil-ex-search)) - (when evil-ex-search-highlight-all - (evil-ex-search-activate-highlight pattern)))) + (when (and (not evil-ex-search-incremental) evil-ex-search-highlight-all) + (evil-ex-search-activate-highlight pattern)) + (when (and evil-ex-search-incremental (not evil-ex-search-persistent-highlight)) + (evil-ex-delete-hl 'evil-ex-search))) (t (goto-char evil-ex-search-start-point) (evil-ex-delete-hl 'evil-ex-search) diff --git a/evil-states.el b/evil-states.el index c15b963d2e..450ad2b5a3 100644 --- a/evil-states.el +++ b/evil-states.el @@ -381,16 +381,17 @@ 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-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)))))) + (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"))) (put 'evil-visual-activate-hook 'permanent-local-hook t) (defun evil-visual-deactivate-hook (&optional command) diff --git a/evil-test-helpers.el b/evil-test-helpers.el index c7eeb71d59..7d67c560d9 100644 --- a/evil-test-helpers.el +++ b/evil-test-helpers.el @@ -167,15 +167,17 @@ raised. Remaining forms are evaluated as-is. `(execute-kbd-macro (apply #'vconcat (mapcar #'listify-key-sequence - (list ,@form))))) + (mapcar #'eval ',form))))) ((memq (car-safe form) '(kbd vconcat)) `(execute-kbd-macro ,form)) - (t form)))) + (t + form)))) (if error-symbol `(should-error ,result :type ',error-symbol) result)))) body))) - (when (buffer-name buffer) (kill-buffer buffer)))))) + (and (buffer-name buffer) + (kill-buffer buffer)))))) (defmacro evil-test-selection (string &optional end-string before-predicate after-predicate) diff --git a/evil-tests.el b/evil-tests.el index b1b3f797b0..5d323b8923 100644 --- a/evil-tests.el +++ b/evil-tests.el @@ -1,4 +1,4 @@ -;; evil-tests.el --- unit tests for Evil -*- coding: utf-8; lexical-binding: t; -*- +;; evil-tests.el --- unit tests for Evil -*- coding: utf-8 -*- ;; Author: Vegard Øye <vegard_oye at hotmail.com> ;; Maintainer: Vegard Øye <vegard_oye at hotmail.com> @@ -61,15 +61,11 @@ ;; ;; This file is NOT part of Evil itself. -;; FIXME: Merely loading an ELisp file should not change Emacs's config! (setq load-prefer-newer t) (require 'cl-lib) (require 'elp) (require 'ert) -;; Load non-compiled `evil-ex'. (It defines `evil-parser' - which is -;; needed by `evil-test-parser' - only inside an `eval-when-compile'.) -(require 'evil-ex "evil-ex.el") (require 'evil) (require 'evil-digraphs) (require 'evil-test-helpers) @@ -85,21 +81,22 @@ (defun evil-tests-initialize (&optional tests profiler interactive) (setq profiler (or profiler evil-tests-profiler)) - (when (consp profiler) + (when (listp profiler) (setq profiler (car profiler))) (when profiler (setq evil-tests-profiler t) (setq profiler - (cdr (assq profiler - '((call . elp-sort-by-call-count) - (average . elp-sort-by-average-time) - (total . elp-sort-by-total-time))))) + (or (cdr (assq profiler + '((call . elp-sort-by-call-count) + (average . elp-sort-by-average-time) + (total . elp-sort-by-total-time)))))) (setq elp-sort-by-function (or profiler 'elp-sort-by-call-count)) (elp-instrument-package "evil")) (if interactive (if (y-or-n-p-with-timeout "Run tests? " 2 t) (evil-tests-run tests interactive) - (message "You can run the tests at any time with `M-x evil-tests-run'")) + (message "You can run the tests at any time \ +with `M-x evil-tests-run'")) (evil-tests-run tests))) (defun evil-tests-run (&optional tests interactive) @@ -204,12 +201,13 @@ (defun evil-test-change-state (state) "Change state to STATE and check keymaps" - (evil-change-state state) - (let ((mode (evil-state-property state :mode)) - ;; (keymap (evil-state-property state :keymap t)) - (local-mode (evil-state-property state :local)) - ;; (local-keymap (evil-state-property state :local-keymap t)) - (tag (evil-state-property state :tag t))) + (let (mode keymap local-mode local-keymap tag) + (evil-change-state state) + (setq mode (evil-state-property state :mode) + keymap (evil-state-property state :keymap t) + local-mode (evil-state-property state :local) + local-keymap (evil-state-property state :local-keymap t) + tag (evil-state-property state :tag t)) (when (functionp tag) (setq tag (funcall tag))) (ert-info ("Update `evil-state'") @@ -445,16 +443,14 @@ when exiting Operator-Pending state") (ert-deftest evil-test-auxiliary-maps () "Test auxiliary keymaps" :tags '(evil state) - ;; `evil-define-key' can't be used on a lexically-scoped keymap var. - (defvar evil--map) - (let ((evil--map (make-sparse-keymap)) aux) + (let ((map (make-sparse-keymap)) aux) (ert-info ("Create a new auxiliary keymap") - (evil-define-key 'normal evil--map "f" 'foo) - (setq aux (evil-get-auxiliary-keymap evil--map 'normal)) + (evil-define-key 'normal map "f" 'foo) + (setq aux (evil-get-auxiliary-keymap map 'normal)) (should (evil-auxiliary-keymap-p aux)) (should (eq (lookup-key aux "f") 'foo))) (ert-info ("Add to auxiliary keymap") - (evil-define-key 'normal evil--map "b" 'bar) + (evil-define-key 'normal map "b" 'bar) (should (eq (lookup-key aux "f") 'foo)) (should (eq (lookup-key aux "b") 'bar))))) @@ -497,8 +493,10 @@ when exiting Operator-Pending state") (let* ((first-line 1) (second-line (progn (forward-line) - (point)))) - (forward-line) + (point))) + (third-line (progn + (forward-line) + (point)))) (ert-info ("Return the beginning and end unchanged \ if they are the same") (should (equal (evil-normalize 1 1 'exclusive) @@ -6035,7 +6033,7 @@ Line 2")) (ert-deftest evil-test-text-object () "Test `evil-define-text-object'" :tags '(evil text-object) - (let ((object (evil-define-text-object nil (count &optional beg end _type) + (let ((object (evil-define-text-object nil (count &optional beg end type) (let ((sel (and beg end (evil-range beg end)))) (when (and sel (> count 0)) (forward-char 1)) (let ((range (if (< count 0) @@ -7439,12 +7437,6 @@ charlie delta <echo foxtrot golf h[o]>tel"))) -(ert-deftest evil-test-visual-separate-from-operator-marks () - "Test that visual selection is kept separate from the '[ and '] marks (#1744)." - (evil-test-buffer "x\ny" - ("ylvpjxgv") - "[x]\n")) - ;;; Replace state (ert-deftest evil-test-replacement () @@ -8541,15 +8533,6 @@ maybe we need one line more with some text\n") ("vj!sort" [return]) "line 5\n[l]ine 3\nline 4\nline 2\nline 1\n"))) -(defmacro evil-with-both-search-modules (&rest body) - `(mapc (lambda (search-module) - (setq evil-search-forward-history nil - evil-search-backward-history nil - evil-ex-search-history nil) - (evil-select-search-module 'evil-search-module search-module) - ,@body) - '(isearch evil-search))) - (ert-deftest evil-test-global () "Test `evil-ex-global'." :tags '(evil ex global) @@ -8918,6 +8901,15 @@ Source (execute-kbd-macro "q:") (should (= (length (window-list)) num-windows)))))) +(defmacro evil-with-both-search-modules (&rest body) + `(mapc (lambda (search-module) + (setq evil-search-forward-history nil + evil-search-backward-history nil + evil-ex-search-history nil) + (evil-select-search-module 'evil-search-module search-module) + ,@body) + '(isearch evil-search))) + (ert-deftest evil-test-command-window-search-history () "Test command window with forward and backward search history" (skip-unless (not noninteractive)) @@ -9129,26 +9121,25 @@ parameter set." (ert-deftest evil-test-properties () "Test `evil-get-property' and `evil-put-property'" :tags '(evil util) - (defvar evil--alist) - (let (evil--alist) + (let (alist) (ert-info ("Set properties") - (evil-put-property 'evil--alist 'wibble :foo t) - (should (equal evil--alist '((wibble . (:foo t))))) - (evil-put-property 'evil--alist 'wibble :bar nil) - (should (equal evil--alist '((wibble . (:foo t :bar nil))))) - (evil-put-property 'evil--alist 'wobble :foo nil :bar nil :baz t) - (should (equal evil--alist '((wobble . (:foo nil :bar nil :baz t)) + (evil-put-property 'alist 'wibble :foo t) + (should (equal alist '((wibble . (:foo t))))) + (evil-put-property 'alist 'wibble :bar nil) + (should (equal alist '((wibble . (:foo t :bar nil))))) + (evil-put-property 'alist 'wobble :foo nil :bar nil :baz t) + (should (equal alist '((wobble . (:foo nil :bar nil :baz t)) (wibble . (:foo t :bar nil)))))) (ert-info ("Get properties") - (should (evil-get-property evil--alist 'wibble :foo)) - (should-not (evil-get-property evil--alist 'wibble :bar)) - (should-not (evil-get-property evil--alist 'wobble :foo)) - (should-not (evil-get-property evil--alist 'wibble :baz)) - (should (equal (evil-get-property evil--alist t :foo) + (should (evil-get-property alist 'wibble :foo)) + (should-not (evil-get-property alist 'wibble :bar)) + (should-not (evil-get-property alist 'wobble :foo)) + (should-not (evil-get-property alist 'wibble :baz)) + (should (equal (evil-get-property alist t :foo) '((wibble . t) (wobble . nil)))) - (should (equal (evil-get-property evil--alist t :bar) + (should (equal (evil-get-property alist t :bar) '((wibble . nil) (wobble . nil)))) - (should (equal (evil-get-property evil--alist t :baz) + (should (equal (evil-get-property alist t :baz) '((wobble . t))))))) (ert-deftest evil-test-filter-list () @@ -9483,26 +9474,26 @@ parameter set." (evil-with-temp-file file-name "" (evil-test-buffer (vconcat "i" file-name [escape]) - (should (not (equal file-name (buffer-file-name)))) + (should (not (equal file-name (buffer-file-name (current-buffer))))) ("gf") - (should (equal file-name (buffer-file-name)))))) + (should (equal file-name (buffer-file-name (current-buffer))))))) (ert-info ("Find file at point (visual state)") (evil-with-temp-file file-name "" (evil-test-buffer (vconcat "iuser@localhost:" file-name "$" [escape]) - (should (not (equal file-name (buffer-file-name)))) + (should (not (equal file-name (buffer-file-name (current-buffer))))) ("0f:lvt$gf") - (should (equal file-name (buffer-file-name)))))) + (should (equal file-name (buffer-file-name (current-buffer))))))) (ert-info ("Find file at point with line number") (let* ((line-number 3) (file-content (make-string (* 2 line-number) ?\n))) (evil-with-temp-file file-name (insert file-content) (evil-test-buffer (vconcat "i" file-name (format ":%d" line-number) [escape]) - (should (and (not (equal file-name (buffer-file-name))) + (should (and (not (equal file-name (buffer-file-name (current-buffer)))) (not (equal line-number (line-number-at-pos))))) ("gF") - (should (and (equal file-name (buffer-file-name)) + (should (and (equal file-name (buffer-file-name (current-buffer))) (equal line-number (line-number-at-pos)))))))) (ert-info ("Find file at point with line and column numbers") (let* ((line-number 3) @@ -9514,11 +9505,11 @@ parameter set." (evil-with-temp-file file-name (insert file-content) (evil-test-buffer (vconcat "i" file-name (format ":%d:%d" line-number column-number) [escape]) - (should (and (not (equal file-name (buffer-file-name))) + (should (and (not (equal file-name (buffer-file-name (current-buffer)))) (not (equal line-number (line-number-at-pos))) (not (equal column-number (current-column))))) ("gF") - (should (and (equal file-name (buffer-file-name)) + (should (and (equal file-name (buffer-file-name (current-buffer))) (equal line-number (line-number-at-pos)) (equal column-number (1+ (current-column)))))))))) @@ -9699,8 +9690,6 @@ main(argc, argv) char **argv; { (ert-deftest evil-test-initial-state () "Test `evil-initial-state'" :tags '(evil core) - ;; FIXME: These have a global effect, so better move them out and give them - ;; a proper namespace prefix. (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) @@ -9730,7 +9719,6 @@ main(argc, argv) char **argv; { ;; 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)))) - ;; FIXME: Same as above. (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) diff --git a/evil-types.el b/evil-types.el index 0e347d00fe..5969ddc346 100644 --- a/evil-types.el +++ b/evil-types.el @@ -373,7 +373,7 @@ If visual state is inactive then those values are nil." (let ((expr (evil-ex-parse (or evil-ex-argument "")))) (if (eq (car expr) 'evil-goto-line) (save-excursion (goto-char evil-ex-point) - (eval (cadr expr) t)) + (eval (cadr expr))) (user-error "Invalid address")))))) (evil-define-interactive-code "<!>" diff --git a/evil-vars.el b/evil-vars.el index f9de9074b6..4d4aa38839 100644 --- a/evil-vars.el +++ b/evil-vars.el @@ -1733,9 +1733,15 @@ instead of `buffer-undo-list'.") (evil-define-local-var evil-visual-point nil "The position of point in Visual state, a marker.") +(evil-define-local-var evil-visual-previous-point nil + "The position of point before Visual state, a marker.") + (evil-define-local-var evil-visual-mark nil "The position of mark in Visual state, a marker.") +(evil-define-local-var evil-visual-previous-mark nil + "The position of mark before Visual state, a marker.") + (evil-define-local-var evil-visual-selection nil "The kind of Visual selection. This is a selection as defined by `evil-define-visual-selection'.") @@ -1903,6 +1909,9 @@ Key sequences bound in this map are immediately executed.") This content of this variable is appended to the Ex command line when Ex is started interactively.") +(defvar evil-ex-commands nil + "Association list of command bindings and functions.") + (defvar evil-ex-history nil "History of Ex commands.") diff --git a/evil.el b/evil.el index e5d7e45991..5bd33e86f9 100644 --- a/evil.el +++ b/evil.el @@ -1,4 +1,4 @@ -;;; evil.el --- Extensible vi layer -*- lexical-binding: t; -*- +;;; evil.el --- extensible vi layer ;; The following list of authors was kept up to date until the beginning of ;; 2017, when evil moved under new maintainers. For authors since then, please @@ -114,7 +114,7 @@ ;; Evil requires undo-redo (Emacs 28), undo-fu or undo-tree for redo ;; functionality. Otherwise, Evil uses regular Emacs undo. ;; -;; https://codeberg.org/ideasman42/emacs-undo-fu +;; https://gitlab.com/ideasman42/emacs-undo-fu ;; https://melpa.org/#/undo-fu ;; https://gitlab.com/tsc25/undo-tree ;; https://elpa.gnu.org/packages/undo-tree.html