diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/evil-20180517.1300/evil-common.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/evil-20180517.1300/evil-common.el | 3883 |
1 files changed, 3883 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/evil-20180517.1300/evil-common.el b/configs/shared/emacs/.emacs.d/elpa/evil-20180517.1300/evil-common.el new file mode 100644 index 000000000000..62eda5fffaa4 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/evil-20180517.1300/evil-common.el @@ -0,0 +1,3883 @@ +;;; evil-common.el --- Common functions and utilities +;; Author: Vegard Øye <vegard_oye at hotmail.com> +;; Maintainer: Vegard Øye <vegard_oye at hotmail.com> + +;; Version: 1.2.13 + +;; +;; This file is NOT part of GNU Emacs. + +;;; License: + +;; This file is part of Evil. +;; +;; Evil is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; Evil is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Evil. If not, see <http://www.gnu.org/licenses/>. + +(require 'evil-vars) +(require 'evil-digraphs) +(require 'rect) +(require 'thingatpt) + +;;; Code: + +(declare-function evil-visual-state-p "evil-states") +(declare-function evil-visual-restore "evil-states") +(declare-function evil-motion-state "evil-states") +(declare-function evil-ex-p "evil-ex") +(declare-function evil-set-jump "evil-jumps") + +(condition-case nil + (require 'windmove) + (error + (message "evil: Could not load `windmove', \ +window commands not available.") + nil)) + +;;; Compatibility with different Emacs versions + +;; x-set-selection and x-get-selection have been deprecated since 25.1 +;; by gui-set-selection and gui-get-selection +(defalias 'evil-get-selection + (if (fboundp 'gui-get-selection) 'gui-get-selection 'x-get-selection)) +(defalias 'evil-set-selection + (if (fboundp 'gui-set-selection) 'gui-set-selection 'x-set-selection)) + +(defmacro evil-called-interactively-p () + "Wrapper for `called-interactively-p'. +In older versions of Emacs, `called-interactively-p' takes +no arguments. In Emacs 23.2 and newer, it takes one argument." + (called-interactively-p 'any)) +(make-obsolete 'evil-called-interactively-p + "please use (called-interactively-p 'any) instead." + "Git commit 222b791") + +;; 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, +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) + +;;; List functions + +(defun evil-add-to-alist (list-var key val &rest elements) + "Add the assocation of KEY and VAL to the value of LIST-VAR. +If the list already contains an entry for KEY, update that entry; +otherwise add at the end of the list." + (let ((tail (symbol-value list-var))) + (while (and tail (not (equal (car-safe (car-safe tail)) key))) + (setq tail (cdr tail))) + (if tail + (setcar tail (cons key val)) + (set list-var (append (symbol-value list-var) + (list (cons key val))))) + (if elements + (apply #'evil-add-to-alist list-var elements) + (symbol-value list-var)))) + +;; custom version of `delete-if' +(defun evil-filter-list (predicate list &optional pointer) + "Delete by side-effect all items satisfying PREDICATE in LIST. +Stop when reaching POINTER. If the first item satisfies PREDICATE, +there is no way to remove it by side-effect; therefore, write +\(setq foo (evil-filter-list 'predicate foo)) to be sure of +changing the value of `foo'." + (let ((tail list) elt head) + (while (and tail (not (eq tail pointer))) + (setq elt (car tail)) + (cond + ((funcall predicate elt) + (setq tail (cdr tail)) + (if head + (setcdr head tail) + (setq list tail))) + (t + (setq head tail + tail (cdr tail))))) + list)) + +(defun evil-member-if (predicate list &optional pointer) + "Find the first item satisfying PREDICATE in LIST. +Stop when reaching POINTER, which should point at a link +in the list." + (let (elt) + (catch 'done + (while (and (consp list) (not (eq list pointer))) + (setq elt (car list)) + (if (funcall predicate elt) + (throw 'done elt) + (setq list (cdr list))))))) + +(defun evil-member-recursive-if (predicate tree) + "Find the first item satisfying PREDICATE in TREE." + (cond + ((funcall predicate tree) + tree) + ((listp tree) + (catch 'done + (dolist (elt tree) + (when (setq elt (evil-member-recursive-if predicate elt)) + (throw 'done elt))))))) + +(defun evil-concat-lists (&rest sequences) + "Concatenate lists, removing duplicates. +Elements are compared with `eq'." + (let (result) + (dolist (sequence sequences) + (dolist (elt sequence) + (add-to-list 'result elt nil #'eq))) + (nreverse result))) + +(defun evil-concat-alists (&rest sequences) + "Concatenate association lists, removing duplicates. +An alist is a list of cons cells (KEY . VALUE) where each key +may occur only once. Later values overwrite earlier values." + (let (result) + (dolist (sequence sequences) + (dolist (elt sequence) + (setq result (assq-delete-all (car-safe elt) result)) + (push elt result))) + (nreverse result))) + +(defun evil-concat-plists (&rest sequences) + "Concatenate property lists, removing duplicates. +A property list is a list (:KEYWORD1 VALUE1 :KEYWORD2 VALUE2...) +where each keyword may occur only once. Later values overwrite +earlier values." + (let (result) + (dolist (sequence sequences result) + (while sequence + (setq result + (plist-put result (pop sequence) (pop sequence))))))) + +(defun evil-concat-keymap-alists (&rest sequences) + "Concatenate keymap association lists, removing duplicates. +A keymap alist is a list of cons cells (VAR . MAP) where each keymap +may occur only once, but where the variables may be repeated +\(e.g., (VAR . MAP1) (VAR . MAP2) is allowed). The order matters, +with the highest priority keymaps being listed first." + (let (result) + (dolist (sequence sequences) + (dolist (elt sequence) + (unless (rassq (cdr-safe elt) result) + (push elt result)))) + (nreverse result))) + +(defun evil-plist-delete (prop plist) + "Delete by side effect the property PROP from PLIST. +If PROP is the first property in PLIST, there is no way +to remove it by side-effect; therefore, write +\(setq foo (evil-plist-delete :prop foo)) to be sure of +changing the value of `foo'." + (let ((tail plist) elt head) + (while tail + (setq elt (car tail)) + (cond + ((eq elt prop) + (setq tail (cdr (cdr tail))) + (if head + (setcdr (cdr head) tail) + (setq plist tail))) + (t + (setq head tail + tail (cdr (cdr tail)))))) + plist)) + +(defun evil-get-property (alist key &optional prop) + "Return property PROP for KEY in ALIST. +ALIST is an association list with entries of the form +\(KEY . PLIST), where PLIST is a property list. +If PROP is nil, return all properties for KEY. +If KEY is t, return an association list of keys +and their PROP values." + (cond + ((null prop) + (cdr (assq key alist))) + ((eq key t) + (let (result val) + (dolist (entry alist result) + (setq key (car entry) + val (cdr entry)) + (when (plist-member val prop) + (setq val (plist-get val prop)) + (push (cons key val) result))))) + (t + (plist-get (cdr (assq key alist)) prop)))) + +(defun evil-put-property (alist-var key prop val &rest properties) + "Set PROP to VAL for KEY in ALIST-VAR. +ALIST-VAR points to an association list with entries of the form +\(KEY . PLIST), where PLIST is a property list storing PROP and VAL." + (set alist-var + (let* ((alist (symbol-value alist-var)) + (plist (cdr (assq key alist)))) + (setq plist (plist-put plist prop val)) + (when properties + (setq plist (evil-concat-plists plist properties) + val (car (last properties)))) + (setq alist (assq-delete-all key alist)) + (push (cons key plist) alist))) + val) + +(defun evil-state-property (state prop &optional value) + "Return the value of property PROP for STATE. +PROP is a keyword as used by `evil-define-state'. +STATE is the state's symbolic name. +If VALUE is non-nil and the value is a variable, +return the value of that variable." + (let ((val (evil-get-property evil-state-properties state prop))) + (if (and value (symbolp val) (boundp val)) + (symbol-value val) + val))) + +(defmacro evil-swap (this that &rest vars) + "Swap the values of variables THIS and THAT. +If three or more arguments are given, the values are rotated. +E.g., (evil-swap A B C) sets A to B, B to C, and C to A." + `(progn + (setq ,this (prog1 ,that + (setq ,that ,this))) + ,@(when vars + `((evil-swap ,that ,@vars))))) + +(defmacro evil-sort (min max &rest vars) + "Place the smallest value in MIN and the largest in MAX. +If three or more arguments are given, place the smallest +value in the first argument and the largest in the last, +sorting in between." + (let ((sorted (make-symbol "sortvar"))) + `(let ((,sorted (sort (list ,min ,max ,@vars) '<))) + (setq ,min (pop ,sorted) + ,max (pop ,sorted) + ,@(apply #'append + (mapcar #'(lambda (var) + (list var `(pop ,sorted))) + vars)))))) + +(defun evil-vector-to-string (vector) + "Turns vector into a string, changing <escape> to '\\e'" + (mapconcat (lambda (c) + (if (equal c 'escape) + "\e" + (make-string 1 c))) + vector + "")) + +;;; Command properties + +(defmacro evil-define-command (command &rest body) + "Define a command COMMAND. + +\(fn COMMAND (ARGS...) DOC [[KEY VALUE]...] BODY...)" + (declare (indent defun) + (debug (&define name + [&optional lambda-list] + [&optional stringp] + [&rest keywordp sexp] + [&optional ("interactive" [&rest form])] + def-body))) + (let ((interactive '(interactive)) + arg args doc doc-form key keys) + ;; collect arguments + (when (listp (car-safe body)) + (setq args (pop body))) + ;; collect docstring + (when (> (length body) 1) + (if (eq (car-safe (car-safe body)) 'format) + (setq doc-form (pop body)) + (when (stringp (car-safe body)) + (setq doc (pop body))))) + ;; collect keywords + (setq keys (plist-put keys :repeat t)) + (while (keywordp (car-safe body)) + (setq key (pop body) + arg (pop body)) + (unless nil ; TODO: add keyword check + (setq keys (plist-put keys key arg)))) + ;; collect `interactive' form + (when (and body (consp (car body)) + (eq (car (car body)) 'interactive)) + (let* ((iform (pop body)) + (result (apply #'evil-interactive-form (cdr iform))) + (form (car result)) + (attrs (cdr result))) + (setq interactive `(interactive ,form) + keys (evil-concat-plists keys attrs)))) + `(progn + ;; the compiler does not recognize `defun' inside `let' + ,(when (and command body) + `(defun ,command ,args + ,@(when doc `(,doc)) + ,interactive + ,@body)) + ,(when (and command doc-form) + `(put ',command 'function-documentation ,doc-form)) + ;; set command properties for symbol or lambda function + (let ((func ',(if (and (null command) body) + `(lambda ,args + ,interactive + ,@body) + command))) + (apply #'evil-set-command-properties func ',keys) + func)))) + +;; If no Evil properties are defined for the command, several parts of +;; Evil apply certain default rules; e.g., the repeat system decides +;; whether the command is repeatable by monitoring buffer changes. +(defun evil-has-command-property-p (command property) + "Whether COMMAND has Evil PROPERTY. +See also `evil-has-command-properties-p'." + (plist-member (evil-get-command-properties command) property)) + +(defun evil-has-command-properties-p (command) + "Whether Evil properties are defined for COMMAND. +See also `evil-has-command-property-p'." + (and (evil-get-command-properties command) t)) + +(defun evil-get-command-property (command property &optional default) + "Return the value of Evil PROPERTY of COMMAND. +If the command does not have the property, return DEFAULT. +See also `evil-get-command-properties'." + (if (evil-has-command-property-p command property) + (evil-get-property evil-command-properties command property) + default)) + +(defun evil-get-command-properties (command) + "Return all Evil properties of COMMAND. +See also `evil-get-command-property'." + (evil-get-property evil-command-properties command)) + +(defun evil-set-command-property (command property value) + "Set PROPERTY to VALUE for COMMAND. +To set multiple properties at once, see +`evil-set-command-properties' and `evil-add-command-properties'." + (evil-put-property 'evil-command-properties command property value)) +(defalias 'evil-put-command-property 'evil-set-command-property) + +(defun evil-add-command-properties (command &rest properties) + "Add PROPERTIES to COMMAND. +PROPERTIES should be a property list. +To replace all properties at once, use `evil-set-command-properties'." + (apply #'evil-put-property + 'evil-command-properties command properties)) + +(defun evil-set-command-properties (command &rest properties) + "Replace all of COMMAND's properties with PROPERTIES. +PROPERTIES should be a property list. +This erases all previous properties; to only add properties, +use `evil-set-command-property'." + (setq evil-command-properties + (assq-delete-all command evil-command-properties)) + (when properties + (apply #'evil-add-command-properties command properties))) + +(defun evil-remove-command-properties (command &rest properties) + "Remove PROPERTIES from COMMAND. +PROPERTIES should be a list of properties (:PROP1 :PROP2 ...). +If PROPERTIES is the empty list, all properties are removed." + (let (plist) + (when properties + (setq plist (evil-get-command-properties command)) + (dolist (property properties) + (setq plist (evil-plist-delete property plist)))) + (apply #'evil-set-command-properties command plist))) + +(defun evil-yank-handler (&optional motion) + "Return the yank handler for MOTION. +MOTION defaults to the current motion." + (setq motion (or motion evil-this-motion)) + (evil-get-command-property motion :yank-handler)) + +(defun evil-declare-motion (command) + "Declare COMMAND to be a movement function. +This ensures that it behaves correctly in Visual state." + (evil-add-command-properties command :keep-visual t :repeat 'motion)) + +(defun evil-declare-repeat (command) + "Declare COMMAND to be repeatable." + (evil-add-command-properties command :repeat t)) + +(defun evil-declare-not-repeat (command) + "Declare COMMAND to be nonrepeatable." + (evil-add-command-properties command :repeat nil)) + +(defun evil-declare-ignore-repeat (command) + "Declare COMMAND to be nonrepeatable." + (evil-add-command-properties command :repeat 'ignore)) + +(defun evil-declare-change-repeat (command) + "Declare COMMAND to be repeatable by buffer changes." + (evil-add-command-properties command :repeat 'change)) + +(defun evil-declare-insert-at-point-repeat (command) + "Declare COMMAND to be repeatable by buffer changes." + (evil-add-command-properties command :repeat 'insert-at-point)) + +(defun evil-declare-abort-repeat (command) + "Declare COMMAND to be nonrepeatable." + (evil-add-command-properties command :repeat 'abort)) + +(defun evil-delimited-arguments (string &optional num) + "Parse STRING as a sequence of delimited arguments. +Returns a list of NUM strings, or as many arguments as +the string contains. The first non-blank character is +taken to be the delimiter. If some arguments are missing +from STRING, the resulting list is padded with nil values. +Two delimiters following directly after each other gives +an empty string." + (save-match-data + (let ((string (or string "")) + (count (or num -1)) (idx 0) + argument delim match result) + (when (string-match "^[[:space:]]*\\([^[:space:]]\\)" string) + (setq delim (match-string 1 string) + argument (format "%s\\(\\(?:[\\].\\|[^%s]\\)*\\)" + (regexp-quote delim) + delim)) + (while (and (/= count 0) (string-match argument string idx)) + (setq match (match-string 1 string) + idx (match-end 1) + count (1- count)) + (when (= count 0) + (unless (save-match-data + (string-match + (format "%s[[:space:]]*$" delim) string idx)) + (setq match (substring string (match-beginning 1))))) + (unless (and (zerop (length match)) + (zerop (length (substring string idx)))) + (push match result)))) + (when (and num (< (length result) num)) + (dotimes (i (- num (length result))) + (push nil result))) + (nreverse result)))) + +(defun evil-concat-charsets (&rest sets) + "Concatenate character sets. +A character set is the part between [ and ] in a regular expression. +If any character set is complemented, the result is also complemented." + (let ((bracket "") (complement "") (hyphen "") result) + (save-match-data + (dolist (set sets) + (when (string-match-p "^\\^" set) + (setq set (substring set 1) + complement "^")) + (when (string-match-p "^]" set) + (setq set (substring set 1) + bracket "]")) + (when (string-match-p "^-" set) + (setq set (substring set 1) + hyphen "-")) + (setq result (concat result set))) + (format "%s%s%s%s" complement bracket hyphen result)))) + +;;; Key sequences + +(defun evil-keypress-parser (&optional input) + "Read from keyboard or INPUT and build a command description. +Returns (CMD COUNT), where COUNT is the numeric prefix argument. +Both COUNT and CMD may be nil." + (let (count negative) + (when input (setq unread-command-events (append input unread-command-events))) + (catch 'done + (while t + (let ((seq (read-key-sequence ""))) + (when seq + (let ((cmd (key-binding seq))) + (cond + ((null cmd) (throw 'done (list nil nil))) + ((arrayp cmd) ; keyboard macro, recursive call + (let ((cmd (evil-keypress-parser cmd))) + (throw 'done + (list (car cmd) + (if (or count (cadr cmd)) + (list (car cmd) (* (or count 1) + (or (cadr cmd) 1)))))))) + ((or (eq cmd #'digit-argument) + (and (eq cmd 'evil-digit-argument-or-evil-beginning-of-line) + count)) + (let* ((event (aref seq (- (length seq) 1))) + (char (or (when (characterp event) event) + (when (symbolp event) + (get event 'ascii-character)))) + (digit (if (or (characterp char) (integerp char)) + (- (logand char ?\177) ?0)))) + (setq count (+ (* 10 (or count 0)) digit)))) + ((eq cmd #'negative-argument) + (setq negative (not negative))) + (t + (throw 'done (list cmd + (and count + (* count + (if negative -1 1)))))))))))))) + +(defun evil-read-key (&optional prompt) + "Read a key from the keyboard. +Translates it according to the input method." + (let ((old-global-map (current-global-map)) + (new-global-map (make-sparse-keymap)) + (overriding-terminal-local-map nil) + (overriding-local-map evil-read-key-map) + seq char cmd) + (unwind-protect + (condition-case nil + (progn + (define-key new-global-map [menu-bar] + (lookup-key global-map [menu-bar])) + (define-key new-global-map [tool-bar] + (lookup-key global-map [tool-bar])) + (add-to-list 'new-global-map + (make-char-table 'display-table + 'self-insert-command) t) + (use-global-map new-global-map) + (setq seq (read-key-sequence prompt nil t) + char (aref seq 0) + cmd (key-binding seq)) + (while (arrayp cmd) + (setq char (aref cmd 0) + cmd (key-binding cmd))) + (cond + ((eq cmd 'self-insert-command) + char) + (cmd + (call-interactively cmd)) + (t + (user-error "No replacement character typed")))) + (quit + (when (fboundp 'evil-repeat-abort) + (evil-repeat-abort)) + (signal 'quit nil))) + (use-global-map old-global-map)))) + +(defun evil-read-quoted-char () + "Command that calls `read-quoted-char'. +This command can be used wherever `read-quoted-char' is required +as a command. Its main use is in the `evil-read-key-map'." + (interactive) + (read-quoted-char)) + +(defun evil-read-digraph-char (&optional hide-chars) + "Read two keys from keyboard forming a digraph. +This function creates an overlay at (point), hiding the next +HIDE-CHARS characters. HIDE-CHARS defaults to 1." + (interactive) + (let (char1 char2 string overlay) + (unwind-protect + (progn + (setq overlay (make-overlay (point) + (min (point-max) + (+ (or hide-chars 1) + (point))))) + (overlay-put overlay 'invisible t) + ;; create overlay prompt + (setq string "?") + (put-text-property 0 1 'face 'minibuffer-prompt string) + ;; put cursor at (i.e., right before) the prompt + (put-text-property 0 1 'cursor t string) + (overlay-put overlay 'after-string string) + (setq char1 (read-key)) + (setq string (string char1)) + (put-text-property 0 1 'face 'minibuffer-prompt string) + (put-text-property 0 1 'cursor t string) + (overlay-put overlay 'after-string string) + (setq char2 (read-key))) + (delete-overlay overlay)) + (or (evil-digraph (list char1 char2)) + ;; use the last character if undefined + char2))) + +(defun evil-read-motion (&optional motion count type modifier) + "Read a MOTION, motion COUNT and motion TYPE from the keyboard. +The type may be overridden with MODIFIER, which may be a type +or a Visual selection as defined by `evil-define-visual-selection'. +Return a list (MOTION COUNT [TYPE])." + (let ((modifiers '((evil-visual-char . char) + (evil-visual-line . line) + (evil-visual-block . block))) + command prefix) + (setq evil-this-type-modified nil) + (unless motion + (while (progn + (setq command (evil-keypress-parser) + motion (pop command) + prefix (pop command)) + (when prefix + (if count + (setq count (string-to-number + (concat (number-to-string count) + (number-to-string prefix)))) + (setq count prefix))) + ;; if the command is a type modifier, read more + (when (rassq motion evil-visual-alist) + (setq modifier + (or modifier + (car (rassq motion evil-visual-alist)))))))) + (when modifier + (setq type (or type (evil-type motion 'exclusive))) + (cond + ((eq modifier 'char) + ;; TODO: this behavior could be less hard-coded + (if (eq type 'exclusive) + (setq type 'inclusive) + (setq type 'exclusive))) + (t + (setq type modifier))) + (setq evil-this-type-modified type)) + (list motion count type))) + +(defun evil-mouse-events-p (keys) + "Returns non-nil iff KEYS contains a mouse event." + (catch 'done + (dotimes (i (length keys)) + (when (or (and (fboundp 'mouse-event-p) + (mouse-event-p (aref keys i))) + (mouse-movement-p (aref keys i))) + (throw 'done t))) + nil)) + +(defun evil-extract-count (keys) + "Splits the key-sequence KEYS into prefix-argument and the rest. +Returns the list (PREFIX CMD SEQ REST), where PREFIX is the +prefix count, CMD the command to be executed, SEQ the subsequence +calling CMD, and REST is all remaining events in the +key-sequence. PREFIX and REST may be nil if they do not exist. +If a command is bound to some keyboard macro, it is expanded +recursively." + (catch 'done + (let* ((len (length keys)) + (beg 0) + (end 1) + (found-prefix nil)) + (while (and (<= end len)) + (let ((cmd (key-binding (substring keys beg end)))) + (cond + ((memq cmd '(undefined nil)) + (user-error "No command bound to %s" (substring keys beg end))) + ((arrayp cmd) ; keyboard macro, replace command with macro + (setq keys (vconcat (substring keys 0 beg) + cmd + (substring keys end)) + end (1+ beg) + len (length keys))) + ((functionp cmd) + (if (or (memq cmd '(digit-argument negative-argument)) + (and found-prefix + (evil-get-command-property + cmd :digit-argument-redirection))) + ;; skip those commands + (setq found-prefix t ; found at least one prefix argument + beg end + end (1+ end)) + ;; a real command, finish + (throw 'done + (list (unless (zerop beg) + (string-to-number + (concat (substring keys 0 beg)))) + cmd + (substring keys beg end) + (when (< end len) + (substring keys end)))))) + (t ; append a further event + (setq end (1+ end)))))) + (user-error "Key sequence contains no complete binding")))) + +(defmacro evil-redirect-digit-argument (map keys target) + "Bind a wrapper function calling TARGET or `digit-argument'. +MAP is a keymap for binding KEYS to the wrapper for TARGET. +The wrapper only calls `digit-argument' if a prefix-argument +has already been started; otherwise TARGET is called." + (let* ((target (eval target)) + (wrapper (intern (format "evil-digit-argument-or-%s" + target)))) + `(progn + (define-key ,map ,keys ',wrapper) + (evil-define-command ,wrapper () + :digit-argument-redirection ,target + :keep-visual t + :repeat nil + (interactive) + (cond + (current-prefix-arg + (setq this-command #'digit-argument) + (call-interactively #'digit-argument)) + (t + (let ((target (or (command-remapping #',target) + #',target))) + (setq this-command target) + (call-interactively target)))))))) + +(defun evil-extract-append (file-or-append) + "Return an (APPEND . FILENAME) pair based on FILE-OR-APPEND. +FILE-OR-APPEND should either be a filename or a \">> FILE\" +directive. APPEND will be t if FILE-OR-APPEND is an append +directive and nil otherwise. FILENAME will be the extracted +filename." + (if (and (stringp file-or-append) + (string-match "\\(>> *\\)" file-or-append)) + (cons t (substring file-or-append(match-end 1))) + (cons nil file-or-append))) + +(defun evil-set-keymap-prompt (map prompt) + "Set the prompt-string of MAP to PROMPT." + (delq (keymap-prompt map) map) + (when prompt + (setcdr map (cons prompt (cdr map))))) + +(defun evil-lookup-key (map key) + "Returns non-nil value if KEY is bound in MAP." + (let ((definition (lookup-key map key))) + (if (numberp definition) ; in-band error + nil + definition))) + +;;; Display + +(defun evil-set-cursor (specs) + "Change the cursor's apperance according to SPECS. +SPECS may be a cursor type as per `cursor-type', a color +string as passed to `set-cursor-color', a zero-argument +function for changing the cursor, or a list of the above." + (unless (and (listp specs) + (null (cdr-safe (last specs)))) + (setq specs (list specs))) + (dolist (spec specs) + (cond + ((functionp spec) + (condition-case nil + (funcall spec) + (error nil))) + ((stringp spec) + (evil-set-cursor-color spec)) + (t + (setq cursor-type spec))))) + +(defun evil-set-cursor-color (color) + "Set the cursor color to COLOR." + (unless (equal (frame-parameter nil 'cursor-color) color) + ;; `set-cursor-color' forces a redisplay, so only + ;; call it when the color actually changes + (set-cursor-color color))) + +(defun evil-refresh-cursor (&optional state buffer) + "Refresh the cursor for STATE in BUFFER. +BUFFER defaults to the current buffer. If STATE is nil the +cursor type is either `evil-force-cursor' or the current state." + (when (and (boundp 'evil-local-mode) evil-local-mode) + (let* ((state (or state evil-force-cursor evil-state 'normal)) + (default (or evil-default-cursor t)) + (cursor (evil-state-property state :cursor t)) + (color (or (and (stringp cursor) cursor) + (and (listp cursor) + (evil-member-if #'stringp cursor)) + (frame-parameter nil 'cursor-color)))) + (with-current-buffer (or buffer (current-buffer)) + ;; if both STATE and `evil-default-cursor' + ;; specify a color, don't set it twice + (when (and color (listp default)) + (setq default (evil-filter-list #'stringp default))) + (evil-set-cursor default) + (evil-set-cursor cursor))))) + +(defmacro evil-save-cursor (&rest body) + "Save the current cursor; execute BODY; restore the cursor." + (declare (indent defun) + (debug t)) + `(let ((cursor cursor-type) + (color (frame-parameter (selected-frame) 'cursor-color)) + (inhibit-quit t)) + (unwind-protect + (progn ,@body) + (evil-set-cursor cursor) + (evil-set-cursor color)))) + +(defun evil-echo (string &rest args) + "Display an unlogged message in the echo area. +That is, the message is not logged in the *Messages* buffer. +\(To log the message, just use `message'.)" + (unless evil-no-display + (let (message-log-max) + (apply #'message string args)))) + +(defun evil-echo-area-save () + "Save the current echo area in `evil-echo-area-message'." + (setq evil-echo-area-message (current-message))) + +(defun evil-echo-area-restore () + "Restore the echo area from `evil-echo-area-message'. +Does not restore if `evil-write-echo-area' is non-nil." + (unless evil-write-echo-area + (if evil-echo-area-message + (message "%s" evil-echo-area-message) + (message nil))) + (setq evil-echo-area-message nil + evil-write-echo-area nil)) + +;; toggleable version of `with-temp-message' +(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)) + `(let ((inhibit-quit t) + evil-echo-area-message + evil-write-echo-area) + (unwind-protect + (progn + (evil-echo-area-save) + ,@body) + (evil-echo-area-restore)))) + +(defmacro evil-without-display (&rest body) + "Execute BODY without Evil displays. +Inhibits echo area messages, mode line updates and cursor changes." + (declare (indent defun) + (debug t)) + `(let ((evil-no-display t)) + ,@body)) + +(defun evil-num-visible-lines () + "Returns the number of currently visible lines." + (- (window-height) 1)) + +(defun evil-count-lines (beg end) + "Return absolute line-number-difference betweeen `beg` and `end`. +This should give the same results no matter where on the line `beg` +and `end` are." + (if (= beg end) + 0 + (let* ((last (max beg end)) + (end-at-bol (save-excursion (goto-char last) + (bolp)))) + (if end-at-bol + (count-lines beg end) + (1- (count-lines beg end)))))) + +;;; Movement + +(defun evil-normalize-position (pos) + "Return POS if it does not exceed the buffer boundaries. +If POS is less than `point-min', return `point-min'. +Is POS is more than `point-max', return `point-max'. +If POS is a marker, return its position." + (cond + ((not (number-or-marker-p pos)) + pos) + ((< pos (point-min)) + (point-min)) + ((> pos (point-max)) + (point-max)) + ((markerp pos) + (marker-position pos)) + (t + pos))) + +(defmacro evil-save-goal-column (&rest body) + "Restores the goal column after execution of BODY. +See also `evil-save-column'." + (declare (indent defun) + (debug t)) + `(let ((goal-column goal-column) + (temporary-goal-column temporary-goal-column)) + ,@body)) + +(defmacro evil-save-column (&rest body) + "Restores the column after execution of BODY. +See also `evil-save-goal-column'." + (declare (indent defun) + (debug t)) + `(let ((col (current-column))) + (evil-save-goal-column + ,@body + (move-to-column col)))) + +(defun evil-narrow (beg end) + "Restrict the buffer to BEG and END. +BEG or END may be nil, specifying a one-sided restriction including +`point-min' or `point-max'. See also `evil-with-restriction.'" + (setq beg (or (evil-normalize-position beg) (point-min))) + (setq end (or (evil-normalize-position end) (point-max))) + (narrow-to-region beg end)) + +(defmacro evil-with-restriction (beg end &rest body) + "Execute BODY with the buffer narrowed to BEG and END. +BEG or END may be nil as passed to `evil-narrow'; this creates +a one-sided restriction." + (declare (indent 2) + (debug t)) + `(save-restriction + (let ((evil-restriction-stack + (cons (cons (point-min) (point-max)) evil-restriction-stack))) + (evil-narrow ,beg ,end) + ,@body))) + +(defmacro evil-without-restriction (&rest body) + "Execute BODY with the top-most narrowing removed. +This works only if the previous narrowing has been generated by +`evil-with-restriction'." + (declare (indent defun) + (debug t)) + `(save-restriction + (widen) + (narrow-to-region (car (car evil-restriction-stack)) + (cdr (car evil-restriction-stack))) + (let ((evil-restriction-stack (cdr evil-restriction-stack))) + ,@body))) + +(defmacro evil-narrow-to-field (&rest body) + "Narrow to the current field." + (declare (indent defun) + (debug t)) + `(evil-with-restriction (field-beginning) (field-end) + ,@body)) + +(defun evil-move-beginning-of-line (&optional arg) + "Move to the beginning of the line as displayed. +Like `move-beginning-of-line', but retains the goal column." + (evil-save-goal-column + (move-beginning-of-line arg) + (beginning-of-line))) + +(defun evil-move-end-of-line (&optional arg) + "Move to the end of the line as displayed. +Like `move-end-of-line', but retains the goal column." + (evil-save-goal-column + (move-end-of-line arg) + (end-of-line))) + +(defun evil-adjust-cursor (&optional force) + "Move point one character back if at the end of a non-empty line. +This behavior is contingent on the variable `evil-move-cursor-back'; +use the FORCE parameter to override it." + (when (and (eolp) + (not evil-move-beyond-eol) + (not (bolp)) + (= (point) + (save-excursion + (evil-move-end-of-line) + (point)))) + (evil-move-cursor-back force))) + +(defun evil-move-cursor-back (&optional force) + "Move point one character back within the current line. +Contingent on the variable `evil-move-cursor-back' or the FORCE +argument. Honors field boundaries, i.e., constrains the movement +to the current field as recognized by `line-beginning-position'." + (when (or evil-move-cursor-back force) + (unless (or (= (point) (line-beginning-position)) + (and (boundp 'visual-line-mode) + visual-line-mode + (= (point) (save-excursion + (beginning-of-visual-line) + (point))))) + (backward-char)))) + +(defun evil-line-position (line &optional column) + "Return the position of LINE. +If COLUMN is specified, return its position on the line. +A negative number means the end of the line." + (save-excursion + (when (fboundp 'evil-goto-line) + (evil-goto-line line)) + (if (numberp column) + (if (< column 0) + (beginning-of-line 2) + (move-to-column column)) + (beginning-of-line)) + (point))) + +(defun evil-column (&optional pos) + "Return the horizontal position of POS. +POS defaults to point." + (save-excursion + (when pos + (goto-char pos)) + (current-column))) + +(defun evil-move-to-column (column &optional dir force) + "Move point to column COLUMN in the current line. +Places point at left of the tab character (at the right if DIR +is non-nil) and returns point." + (interactive "p") + (move-to-column column force) + (unless force + (when (or (not dir) (and (numberp dir) (< dir 1))) + (when (> (current-column) column) + (evil-move-cursor-back)))) + (point)) + +(defmacro evil-loop (spec &rest body) + "Loop with countdown variable. +Evaluate BODY with VAR counting down from COUNT to 0. +COUNT can be negative, in which case VAR counts up instead. +The return value is the value of VAR when the loop +terminates, which is 0 if the loop completes successfully. +RESULT specifies a variable for storing this value. + +\(fn (VAR COUNT [RESULT]) BODY...)" + (declare (indent defun) + (debug dolist)) + (let* ((i (make-symbol "loopvar")) + (var (pop spec)) + (count (pop spec)) + (result (pop spec))) + (setq var (or (unless (eq var result) var) i) + result (or result var)) + `(let ((,var ,count)) + (setq ,result ,var) + (while (/= ,var 0) + ,@body + (if (> ,var 0) + (setq ,var (1- ,var)) + (setq ,var (1+ ,var))) + (setq ,result ,var)) + ,var))) + +;;; Motions + +(defmacro evil-motion-loop (spec &rest body) + "Loop a certain number of times. +Evaluate BODY repeatedly COUNT times with VAR bound to 1 or -1, +depending on the sign of COUNT. RESULT, if specified, holds +the number of unsuccessful iterations, which is 0 if the loop +completes successfully. This is also the return value. + +Each iteration must move point; if point does not change, +the loop immediately quits. See also `evil-loop'. + +\(fn (VAR COUNT [RESULT]) BODY...)" + (declare (indent defun) + (debug ((symbolp form &optional symbolp) body))) + (let* ((var (or (pop spec) (make-symbol "unitvar"))) + (countval (or (pop spec) 0)) + (result (pop spec)) + (i (make-symbol "loopvar")) + (count (make-symbol "countvar")) + (done (make-symbol "donevar")) + (orig (make-symbol "origvar"))) + `(let* ((,count ,countval) + (,var (if (< ,count 0) -1 1))) + (catch ',done + (evil-loop (,i ,count ,result) + (let ((,orig (point))) + ,@body + (when (= (point) ,orig) + (throw ',done ,i)))))))) + +(defmacro evil-signal-without-movement (&rest body) + "Catches errors provided point moves within this scope." + (declare (indent defun) + (debug t)) + `(let ((p (point))) + (condition-case err + (progn ,@body) + (error + (when (= p (point)) + (signal (car err) (cdr err))))))) + +(defun evil-signal-at-bob-or-eob (&optional count) + "Signals error if `point' is at boundaries. +If `point' is at bob and COUNT is negative this function signal +'beginning-of-buffer. If `point' is at eob and COUNT is positive +this function singal 'end-of-buffer. This function should be used +in motions. COUNT defaults to 1." + (setq count (or count 1)) + (cond + ((< count 0) (evil-signal-at-bob)) + ((> count 0) (evil-signal-at-eob)))) + +(defun evil-signal-at-bob () + "Signals 'beginning-of-buffer if `point' is at bob. +This function should be used in backward motions. If `point' is at +bob so that no further backward motion is possible the error +'beginning-of-buffer is raised." + (when (bobp) (signal 'beginning-of-buffer nil))) + +(defun evil-signal-at-eob () + "Signals 'end-of-buffer if `point' is at eob. +This function should be used in forward motions. If `point' is close +to eob so that no further forward motion is possible the error +'end-of-buffer is raised. This is the case if `point' is at +`point-max' or if is one position before `point-max', +`evil-move-cursor-back' is non-nil and `point' is not at the end +of a line. The latter is necessary because `point' cannot be +moved to `point-max' if `evil-move-cursor-back' is non-nil and +the last line in the buffer is not empty." + (when (or (eobp) + (and (not (eolp)) + evil-move-cursor-back + (save-excursion (forward-char) (eobp)))) + (signal 'end-of-buffer nil))) + +(defmacro evil-with-hproject-point-on-window (&rest body) + "Project point after BODY to current window. +If point is on a position left or right of the current window +then it is moved to the left and right boundary of the window, +respectively. If `auto-hscroll-mode' is non-nil then the left and +right positions are increased or decreased, respectively, by +`horizontal-margin' so that no automatic scrolling occurs." + (declare (indent defun) + (debug t)) + (let ((diff (make-symbol "diff")) + (left (make-symbol "left")) + (right (make-symbol "right"))) + `(let ((,diff (if auto-hscroll-mode (1+ hscroll-margin) 0)) + auto-hscroll-mode) + ,@body + (let* ((,left (+ (window-hscroll) ,diff)) + (,right (+ (window-hscroll) (window-width) (- ,diff) -1))) + (move-to-column (min (max (current-column) ,left) ,right)))))) + +(defun evil-goto-min (&rest positions) + "Go to the smallest position in POSITIONS. +Non-numerical elements are ignored. +See also `evil-goto-max'." + (when (setq positions (evil-filter-list + #'(lambda (elt) + (not (number-or-marker-p elt))) + positions)) + (goto-char (apply #'min positions)))) + +(defun evil-goto-max (&rest positions) + "Go to the largest position in POSITIONS. +Non-numerical elements are ignored. +See also `evil-goto-min'." + (when (setq positions (evil-filter-list + #'(lambda (elt) + (not (number-or-marker-p elt))) + positions)) + (goto-char (apply #'max positions)))) + +(defun evil-forward-not-thing (thing &optional count) + "Move point to the end or beginning of the complement of THING." + (evil-motion-loop (dir (or count 1)) + (let (bnd) + (cond + ((> dir 0) + (while (and (setq bnd (bounds-of-thing-at-point thing)) + (< (point) (cdr bnd))) + (goto-char (cdr bnd))) + ;; no thing at (point) + (if (zerop (forward-thing thing)) + ;; now at the end of the next thing + (let ((bnd (bounds-of-thing-at-point thing))) + (if (or (< (car bnd) (point)) ; end of a thing + (= (car bnd) (cdr bnd))) ; zero width thing + (goto-char (car bnd)) + ;; beginning of yet another thing, go back + (forward-thing thing -1))) + (goto-char (point-max)))) + (t + (while (and (not (bobp)) + (or (backward-char) t) + (setq bnd (bounds-of-thing-at-point thing)) + (< (point) (cdr bnd))) + (goto-char (car bnd))) + ;; either bob or no thing at point + (goto-char + (if (and (not (bobp)) + (zerop (forward-thing thing -1)) + (setq bnd (bounds-of-thing-at-point thing))) + (cdr bnd) + (point-min)))))))) + +(defun evil-bounds-of-not-thing-at-point (thing &optional which) + "Returns the bounds of a complement of THING at point. +If there is a THING at point nil is returned. Otherwise if WHICH +is nil or 0 a cons cell (BEG . END) is returned. If WHICH is +negative the beginning is returned. If WHICH is positive the END +is returned." + (let ((pnt (point))) + (let ((beg (save-excursion + (and (zerop (forward-thing thing -1)) + (forward-thing thing)) + (if (> (point) pnt) (point-min) (point)))) + (end (save-excursion + (and (zerop (forward-thing thing)) + (forward-thing thing -1)) + (if (< (point) pnt) (point-max) (point))))) + (when (and (<= beg (point)) (<= (point) end) (< beg end)) + (cond + ((or (not which) (zerop which)) (cons beg end)) + ((< which 0) beg) + ((> which 0) end)))))) + +(defun evil-forward-nearest (count &rest forwards) + "Moves point forward to the first of several motions. +FORWARDS is a list of forward motion functions (i.e. each moves +point forward to the next end of a text object (if passed a +1) +or backward to the preceeding beginning of a text object (if +passed a -1)). This function calls each of these functions once +and moves point to the nearest of the resulting positions. If +COUNT is positive point is moved forward COUNT times, if negative +point is moved backward -COUNT times." + (evil-motion-loop (dir (or count 1)) + (let ((pnt (point)) + (nxt (if (> dir 0) (point-max) (point-min)))) + (dolist (fwd forwards) + (goto-char pnt) + (condition-case nil + (evil-with-restriction + (and (< dir 0) + (save-excursion + (goto-char nxt) + (line-beginning-position 0))) + (and (> dir 0) + (save-excursion + (goto-char nxt) + (line-end-position 2))) + (if (and (zerop (funcall fwd dir)) + (/= (point) pnt) + (or (and (> dir 0) (< (point) nxt)) + (and (< dir 0) (> (point) nxt)))) + (setq nxt (point)))) + (error))) + (goto-char nxt)))) + +(defun bounds-of-evil-string-at-point (&optional state) + "Return the bounds of a string at point. +If STATE is given it used a parsing state at point." + (save-excursion + (let ((state (or state (syntax-ppss)))) + (and (nth 3 state) + (cons (nth 8 state) + (and (parse-partial-sexp (point) + (point-max) + nil + nil + state + 'syntax-table) + (point))))))) +(put 'evil-string 'bounds-of-thing-at-point #'bounds-of-evil-string-at-point) + +(defun bounds-of-evil-comment-at-point () + "Return the bounds of a string at point." + (save-excursion + (let ((state (syntax-ppss))) + (and (nth 4 state) + (cons (nth 8 state) + (and (parse-partial-sexp (point) + (point-max) + nil + nil + state + 'syntax-table) + (point))))))) +(put 'evil-comment 'bounds-of-thing-at-point #'bounds-of-evil-comment-at-point) + +;; The purpose of this function is the provide line motions which +;; preserve the column. This is how `previous-line' and `next-line' +;; work, but unfortunately the behaviour is hard-coded: if and only if +;; the last command was `previous-line' or `next-line', the column is +;; preserved. Furthermore, in contrast to Vim, when we cannot go +;; further, those motions move point to the beginning resp. the end of +;; the line (we never want point to leave its column). The code here +;; comes from simple.el, and I hope it will work in future. +(defun evil-line-move (count &optional noerror) + "A wrapper for line motions which conserves the column. +Signals an error at buffer boundaries unless NOERROR is non-nil." + (cond + (noerror + (condition-case nil + (evil-line-move count) + (error nil))) + (t + (evil-signal-without-movement + (setq this-command (if (>= count 0) + #'next-line + #'previous-line)) + (let ((opoint (point))) + (condition-case err + (with-no-warnings + (funcall this-command (abs count))) + ((beginning-of-buffer end-of-buffer) + (let ((col (or goal-column + (if (consp temporary-goal-column) + (car temporary-goal-column) + temporary-goal-column)))) + (if line-move-visual + (vertical-motion (cons col 0)) + (line-move-finish col opoint (< count 0))) + ;; Maybe we should just `ding'? + (signal (car err) (cdr err)))))))))) + +(defun evil-forward-syntax (syntax &optional count) + "Move point to the end or beginning of a sequence of characters in +SYNTAX. +Stop on reaching a character not in SYNTAX." + (let ((notsyntax (if (= (aref syntax 0) ?^) + (substring syntax 1) + (concat "^" syntax)))) + (evil-motion-loop (dir (or count 1)) + (cond + ((< dir 0) + (skip-syntax-backward notsyntax) + (skip-syntax-backward syntax)) + (t + (skip-syntax-forward notsyntax) + (skip-syntax-forward syntax)))))) + +(defun evil-forward-chars (chars &optional count) + "Move point to the end or beginning of a sequence of CHARS. +CHARS is a character set as inside [...] in a regular expression." + (let ((notchars (if (= (aref chars 0) ?^) + (substring chars 1) + (concat "^" chars)))) + (evil-motion-loop (dir (or count 1)) + (cond + ((< dir 0) + (skip-chars-backward notchars) + (skip-chars-backward chars)) + (t + (skip-chars-forward notchars) + (skip-chars-forward chars)))))) + +(defun evil-up-block (beg end &optional count) + "Move point to the end or beginning of text enclosed by BEG and END. +BEG and END should be regular expressions matching the opening +and closing delimiters, respectively. If COUNT is greater than +zero point is moved forward otherwise it is moved +backwards. Whenever an opening delimiter is found the COUNT is +increased by one, if a closing delimiter is found the COUNT is +decreased by one. The motion stops when COUNT reaches zero. The +match-data reflects the last successful match (that caused COUNT +to reach zero). The behaviour of this functions is similar to +`up-list'." + (let* ((count (or count 1)) + (forwardp (> count 0)) + (dir (if forwardp +1 -1))) + (catch 'done + (while (not (zerop count)) + (let* ((pnt (point)) + (cl (save-excursion + (and (re-search-forward (if forwardp end beg) nil t dir) + (or (/= pnt (point)) + (progn + ;; zero size match, repeat search from + ;; the next position + (forward-char dir) + (re-search-forward (if forwardp end beg) nil t dir))) + (point)))) + (match (match-data t)) + (op (save-excursion + (and (not (equal beg end)) + (re-search-forward (if forwardp beg end) cl t dir) + (or (/= pnt (point)) + (progn + ;; zero size match, repeat search from + ;; the next position + (forward-char dir) + (re-search-forward (if forwardp beg end) cl t dir))) + (point))))) + (cond + ((not cl) + (goto-char (if forwardp (point-max) (point-min))) + (set-match-data nil) + (throw 'done count)) + (t + (if op + (progn + (setq count (if forwardp (1+ count) (1- count))) + (goto-char op)) + (setq count (if forwardp (1- count) (1+ count))) + (if (zerop count) (set-match-data match)) + (goto-char cl)))))) + 0))) + +(defun evil-up-paren (open close &optional count) + "Move point to the end or beginning of balanced parentheses. +OPEN and CLOSE should be characters identifying the opening and +closing parenthesis, respectively. If COUNT is greater than zero +point is moved forward otherwise it is moved backwards. Whenever +an opening delimiter is found the COUNT is increased by one, if a +closing delimiter is found the COUNT is decreased by one. The +motion stops when COUNT reaches zero. The match-data reflects the +last successful match (that caused COUNT to reach zero)." + ;; Always use the default `forward-sexp-function'. This is important + ;; for modes that use a custom one like `python-mode'. + ;; (addresses #364) + (let (forward-sexp-function) + (with-syntax-table (copy-syntax-table (syntax-table)) + (modify-syntax-entry open (format "(%c" close)) + (modify-syntax-entry close (format ")%c" open)) + (let ((rest (evil-motion-loop (dir count) + (let ((pnt (point))) + (condition-case nil + (cond + ((> dir 0) + (while (progn + (up-list dir) + (/= (char-before) close)))) + (t + (while (progn + (up-list dir) + (/= (char-after) open))))) + (error (goto-char pnt))))))) + (cond + ((= rest count) (set-match-data nil)) + ((> count 0) (set-match-data (list (1- (point)) (point)))) + (t (set-match-data (list (point) (1+ (point)))))) + rest)))) + +(defun evil-up-xml-tag (&optional count) + "Move point to the end or beginning of balanced xml tags. +OPEN and CLOSE should be characters identifying the opening and +closing parenthesis, respectively. If COUNT is greater than zero +point is moved forward otherwise it is moved backwards. Whenever +an opening delimiter is found the COUNT is increased by one, if a +closing delimiter is found the COUNT is decreased by one. The +motion stops when COUNT reaches zero. The match-data reflects the +last successful match (that caused COUNT to reach zero)." + (let* ((dir (if (> (or count 1) 0) +1 -1)) + (count (abs (or count 1))) + (match (> count 0)) + (op (if (> dir 0) 1 2)) + (cl (if (> dir 0) 2 1)) + (orig (point)) + pnt tags match) + (catch 'done + (while (> count 0) + ;; find the previous opening tag + (while + (and (setq match + (re-search-forward + "<\\([^/ >]+\\)\\(?:[^\"/>]\\|\"[^\"]*\"\\)*?>\\|</\\([^>]+?\\)>" + nil t dir)) + (cond + ((match-beginning op) + (push (match-string op) tags)) + ((null tags) nil) ; free closing tag + ((and (< dir 0) + (string= (car tags) (match-string cl))) + ;; in backward direction we only accept matching + ;; tags. If the current tag is a free opener + ;; without matching closing tag, the subsequents + ;; test will make us ignore this tag + (pop tags)) + ((and (> dir 0)) + ;; non matching openers are considered free openers + (while (and tags + (not (string= (car tags) + (match-string cl)))) + (pop tags)) + (pop tags))))) + (unless (setq match (and match (match-data t))) + (setq match nil) + (throw 'done count)) + ;; found closing tag, look for corresponding opening tag + (cond + ((> dir 0) + (setq pnt (match-end 0)) + (goto-char (match-beginning 0))) + (t + (setq pnt (match-beginning 0)) + (goto-char (match-end 0)))) + (let* ((tag (match-string cl)) + (refwd (concat "<\\(/\\)?" + (regexp-quote tag) + "\\(?:>\\| \\(?:[^\"/>]\\|\"[^\"]*\"\\)*?>\\)")) + (cnt 1)) + (while (and (> cnt 0) (re-search-backward refwd nil t dir)) + (setq cnt (+ cnt (if (match-beginning 1) dir (- dir))))) + (if (zerop cnt) (setq count (1- count) tags nil)) + (goto-char pnt))) + (if (> count 0) + (set-match-data nil) + (set-match-data match) + (goto-char (if (> dir 0) (match-end 0) (match-beginning 0))))) + ;; if not found, set to point-max/point-min + (unless (zerop count) + (set-match-data nil) + (goto-char (if (> dir 0) (point-max) (point-min))) + (if (/= (point) orig) (setq count (1- count)))) + (* dir count))) + +(defun evil-forward-quote (quote &optional count) + "Move point to the end or beginning of a string. +QUOTE is the character delimiting the string. If COUNT is greater +than zero point is moved forward otherwise it is moved +backwards." + (let (reset-parser) + (with-syntax-table (copy-syntax-table (syntax-table)) + (unless (= (char-syntax quote) ?\") + (modify-syntax-entry quote "\"") + (setq reset-parser t)) + ;; global parser state is out of state, use local one + (let* ((pnt (point)) + (state (save-excursion + (beginning-of-defun) + (parse-partial-sexp (point) pnt nil nil (syntax-ppss)))) + (bnd (bounds-of-evil-string-at-point state))) + (when (and bnd (< (point) (cdr bnd))) + ;; currently within a string + (if (> count 0) + (progn + (goto-char (cdr bnd)) + (setq count (1- count))) + (goto-char (car bnd)) + (setq count (1+ count)))) + ;; forward motions work with local parser state + (cond + ((> count 0) + ;; no need to reset global parser state because we only use + ;; the local one + (setq reset-parser nil) + (catch 'done + (while (and (> count 0) (not (eobp))) + (setq state (parse-partial-sexp (point) (point-max) + nil + nil + state + 'syntax-table)) + (cond + ((nth 3 state) + (setq bnd (bounds-of-thing-at-point 'evil-string)) + (goto-char (cdr bnd)) + (setq count (1- count))) + ((eobp) (goto-char pnt) (throw 'done nil)))))) + ((< count 0) + ;; need to update global cache because of backward motion + (setq reset-parser (and reset-parser (point))) + (save-excursion + (beginning-of-defun) + (syntax-ppss-flush-cache (point))) + (catch 'done + (while (and (< count 0) (not (bobp))) + (setq pnt (point)) + (while (and (not (bobp)) + (or (eobp) (/= (char-after) quote))) + (backward-char)) + (cond + ((setq bnd (bounds-of-thing-at-point 'evil-string)) + (goto-char (car bnd)) + (setq count (1+ count))) + ((bobp) (goto-char pnt) (throw 'done nil)) + (t (backward-char)))))) + (t (setq reset-parser nil))))) + (when reset-parser + ;; reset global cache + (save-excursion + (goto-char reset-parser) + (beginning-of-defun) + (syntax-ppss-flush-cache (point)))) + count)) + +;;; Thing-at-point motion functions for Evil text objects and motions +(defun forward-evil-empty-line (&optional count) + "Move forward COUNT empty lines." + (setq count (or count 1)) + (cond + ((> count 0) + (while (and (> count 0) (not (eobp))) + (when (and (bolp) (eolp)) + (setq count (1- count))) + (forward-line 1))) + (t + (while (and (< count 0) (not (bobp)) + (zerop (forward-line -1))) + (when (and (bolp) (eolp)) + (setq count (1+ count)))))) + count) + +(defun forward-evil-space (&optional count) + "Move forward COUNT whitespace sequences [[:space:]]+." + (evil-forward-chars "[:space:]" count)) + +(defun forward-evil-word (&optional count) + "Move forward COUNT words. +Moves point COUNT words forward or (- COUNT) words backward if +COUNT is negative. Point is placed after the end of the word (if +forward) or at the first character of the word (if backward). A +word is a sequence of word characters matching +\[[:word:]] (recognized by `forward-word'), a sequence of +non-whitespace non-word characters '[^[:word:]\\n\\r\\t\\f ]', or +an empty line matching ^$." + (evil-forward-nearest + count + #'(lambda (&optional cnt) + (let ((word-separating-categories evil-cjk-word-separating-categories) + (word-combining-categories evil-cjk-word-combining-categories) + (pnt (point))) + (forward-word cnt) + (if (= pnt (point)) cnt 0))) + #'(lambda (&optional cnt) + (evil-forward-chars "^[:word:]\n\r\t\f " cnt)) + #'forward-evil-empty-line)) + +(defun forward-evil-WORD (&optional count) + "Move forward COUNT \"WORDS\". +Moves point COUNT WORDS forward or (- COUNT) WORDS backward if +COUNT is negative. Point is placed after the end of the WORD (if +forward) or at the first character of the WORD (if backward). A +WORD is a sequence of non-whitespace characters +'[^\\n\\r\\t\\f ]', or an empty line matching ^$." + (evil-forward-nearest count + #'(lambda (&optional cnt) + (evil-forward-chars "^\n\r\t\f " cnt)) + #'forward-evil-empty-line)) + +(defun forward-evil-symbol (&optional count) + "Move forward COUNT symbols. +Moves point COUNT symbols forward or (- COUNT) symbols backward +if COUNT is negative. Point is placed after the end of the +symbol (if forward) or at the first character of the symbol (if +backward). A symbol is either determined by `forward-symbol', or +is a sequence of characters not in the word, symbol or whitespace +syntax classes." + (evil-forward-nearest + count + #'(lambda (&optional cnt) + (evil-forward-syntax "^w_->" cnt)) + #'(lambda (&optional cnt) + (let ((pnt (point))) + (forward-symbol cnt) + (if (= pnt (point)) cnt 0))) + #'forward-evil-empty-line)) + +(defun forward-evil-defun (&optional count) + "Move forward COUNT defuns. +Moves point COUNT defuns forward or (- COUNT) defuns backward +if COUNT is negative. A defun is defined by +`beginning-of-defun' and `end-of-defun' functions." + (evil-motion-loop (dir (or count 1)) + (if (> dir 0) (end-of-defun) (beginning-of-defun)))) + +(defun forward-evil-sentence (&optional count) + "Move forward COUNT sentences. +Moves point COUNT sentences forward or (- COUNT) sentences +backward if COUNT is negative. This function is the same as +`forward-sentence' but returns the number of sentences that could +NOT be moved over." + (evil-motion-loop (dir (or count 1)) + (condition-case nil + (forward-sentence dir) + (error)))) + +(defun forward-evil-paragraph (&optional count) + "Move forward COUNT paragraphs. +Moves point COUNT paragraphs forward or (- COUNT) paragraphs backward +if COUNT is negative. A paragraph is defined by +`start-of-paragraph-text' and `forward-paragraph' functions." + (evil-motion-loop (dir (or count 1)) + (cond + ((> dir 0) (forward-paragraph)) + ((not (bobp)) (start-of-paragraph-text) (beginning-of-line))))) + +(defvar evil-forward-quote-char ?\" + "The character to be used by `forward-evil-quote'.") + +(defun forward-evil-quote (&optional count) + "Move forward COUNT strings. +The quotation character is specified by the global variable +`evil-forward-quote-char'. This character is passed to +`evil-forward-quote'." + (evil-forward-quote evil-forward-quote-char count)) + +(defun forward-evil-quote-simple (&optional count) + "Move forward COUNT strings. +The quotation character is specified by the global variable +`evil-forward-quote-char'. This functions uses Vim's rules +parsing from the beginning of the current line for quotation +characters. It should only be used when looking for strings +within comments and buffer *must* be narrowed to the comment." + (let ((dir (if (> (or count 1) 0) 1 -1)) + (ch evil-forward-quote-char) + (pnt (point)) + (cnt 0)) + (beginning-of-line) + ;; count number of quotes before pnt + (while (< (point) pnt) + (when (= (char-after) ch) + (setq cnt (1+ cnt))) + (forward-char)) + (setq cnt (- (* 2 (abs count)) (mod cnt 2))) + (cond + ((> dir 0) + (while (and (not (eolp)) (not (zerop cnt))) + (when (= (char-after) ch) (setq cnt (1- cnt))) + (forward-char)) + (when (not (zerop cnt)) (goto-char (point-max)))) + (t + (while (and (not (bolp)) (not (zerop cnt))) + (when (= (char-before) ch) (setq cnt (1- cnt))) + (forward-char -1)) + (when (not (zerop cnt)) (goto-char (point-min))))) + (/ cnt 2))) + +;;; Motion functions +(defun evil-forward-beginning (thing &optional count) + "Move forward to beginning of THING. +The motion is repeated COUNT times." + (setq count (or count 1)) + (if (< count 0) + (forward-thing thing count) + (let ((bnd (bounds-of-thing-at-point thing)) + rest) + (when (and bnd (< (point) (cdr bnd))) + (goto-char (cdr bnd))) + (condition-case nil + (when (zerop (setq rest (forward-thing thing count))) + (when (and (bounds-of-thing-at-point thing) + (not (bobp)) + ;; handle final empty line + (not (and (bolp) (eobp)))) + (forward-char -1)) + (beginning-of-thing thing)) + (error)) + rest))) + +(defun evil-backward-beginning (thing &optional count) + "Move backward to beginning of THING. +The motion is repeated COUNT times. This is the same as calling +`evil-backward-beginning' with -COUNT." + (evil-forward-beginning thing (- (or count 1)))) + +(defun evil-forward-end (thing &optional count) + "Move forward to end of THING. +The motion is repeated COUNT times." + (setq count (or count 1)) + (cond + ((> count 0) + (unless (eobp) (forward-char)) + (prog1 (forward-thing thing count) + (unless (bobp) (forward-char -1)))) + (t + (let ((bnd (bounds-of-thing-at-point thing)) + rest) + (when (and bnd (< (point) (cdr bnd) )) + (goto-char (car bnd))) + (condition-case nil + (when (zerop (setq rest (forward-thing thing count))) + (end-of-thing thing) + (forward-char -1)) + (error)) + rest)))) + +(defun evil-backward-end (thing &optional count) + "Move backward to end of THING. +The motion is repeated COUNT times. This is the same as calling +`evil-backward-end' with -COUNT." + (evil-forward-end thing (- (or count 1)))) + +(defun evil-forward-word (&optional count) + "Move by words. +Moves point COUNT words forward or (- COUNT) words backward if +COUNT is negative. This function is the same as `forward-word' +but returns the number of words by which point could *not* be +moved." + (setq count (or count 1)) + (let* ((dir (if (>= count 0) +1 -1)) + (count (abs count))) + (while (and (> count 0) + (forward-word dir)) + (setq count (1- count))) + count)) + +(defun evil-in-comment-p (&optional pos) + "Checks if POS is within a comment according to current syntax. +If POS is nil, (point) is used. The return value is the beginning +position of the comment." + (setq pos (or pos (point))) + (let ((chkpos + (cond + ((eobp) pos) + ((= (char-syntax (char-after)) ?<) (1+ pos)) + ((and (not (zerop (logand (car (syntax-after (point))) + (lsh 1 16)))) + (not (zerop (logand (or (car (syntax-after (1+ (point)))) 0) + (lsh 1 17))))) + (+ pos 2)) + ((and (not (zerop (logand (car (syntax-after (point))) + (lsh 1 17)))) + (not (zerop (logand (or (car (syntax-after (1- (point)))) 0) + (lsh 1 16))))) + (1+ pos)) + (t pos)))) + (let ((syn (save-excursion (syntax-ppss chkpos)))) + (and (nth 4 syn) (nth 8 syn))))) + +(defun evil-looking-at-start-comment (&optional move) + "Returns t if point is at the start of a comment. +point must be on one of the opening characters of a block comment +according to the current syntax table. Futhermore these +characters must been parsed as opening characters, i.e. they +won't be considered as comment starters inside a string or +possibly another comment. Point is moved to the first character +of the comment opener if MOVE is non-nil." + (cond + ;; one character opener + ((= (char-syntax (char-after)) ?<) + (equal (point) (evil-in-comment-p (1+ (point))))) + ;; two character opener on first char + ((and (not (zerop (logand (car (syntax-after (point))) + (lsh 1 16)))) + (not (zerop (logand (or (car (syntax-after (1+ (point)))) 0) + (lsh 1 17))))) + (equal (point) (evil-in-comment-p (+ 2 (point))))) + ;; two character opener on second char + ((and (not (zerop (logand (car (syntax-after (point))) + (lsh 1 17)))) + (not (zerop (logand (or (car (syntax-after (1- (point)))) 0) + (lsh 1 16))))) + (and (equal (1- (point)) (evil-in-comment-p (1+ (point)))) + (prog1 t (when move (backward-char))))))) + +(defun evil-looking-at-end-comment (&optional move) + "Returns t if point is at the end of a comment. +point must be on one of the opening characters of a block comment +according to the current syntax table. Futhermore these +characters must been parsed as opening characters, i.e. they +won't be considered as comment starters inside a string or +possibly another comment. Point is moved right after the comment +closer if MOVE is non-nil." + (cond + ;; one char closer + ((= (char-syntax (char-after)) ?>) + (and (evil-in-comment-p) ; in comment + (not (evil-in-comment-p (1+ (point)))) + (prog1 t (when move (forward-char))))) + ;; two char closer on first char + ((and (not (zerop (logand (car (syntax-after (point))) + (lsh 1 18)))) + (not (zerop (logand (or (car (syntax-after (1+ (point)))) 0) + (lsh 1 19))))) + (and (evil-in-comment-p) + (not (evil-in-comment-p (+ (point) 2))) + (prog1 t (when move (forward-char 2))))) + ;; two char closer on second char + ((and (not (zerop (logand (car (syntax-after (point))) + (lsh 1 19)))) + (not (zerop (logand (or (car (syntax-after (1- (point)))) 0) + (lsh 1 18))))) + (and (evil-in-comment-p) + (not (evil-in-comment-p (1+ (point)))) + (prog1 t (when move (forward-char))))))) + +(defun evil-insert-newline-above () + "Inserts a new line above point and places point in that line +with regard to indentation." + (evil-narrow-to-field + (evil-move-beginning-of-line) + (insert (if use-hard-newlines hard-newline "\n")) + (forward-line -1) + (back-to-indentation))) + +(defun evil-insert-newline-below () + "Inserts a new line below point and places point in that line +with regard to indentation." + (evil-narrow-to-field + (evil-move-end-of-line) + (insert (if use-hard-newlines hard-newline "\n")) + (back-to-indentation))) + +;;; Markers + +(defun evil-global-marker-p (char) + "Whether CHAR denotes a global marker." + (or (and (>= char ?A) (<= char ?Z)) + (assq char (default-value 'evil-markers-alist)))) + +(defun evil-set-marker (char &optional pos advance) + "Set the marker denoted by CHAR to position POS. +POS defaults to the current position of point. +If ADVANCE is t, the marker advances when inserting text at it; +otherwise, it stays behind." + (interactive (list (read-char))) + (catch 'done + (let ((marker (evil-get-marker char t)) alist) + (unless (markerp marker) + (cond + ((and marker (symbolp marker) (boundp marker)) + (set marker (or (symbol-value marker) (make-marker))) + (setq marker (symbol-value marker))) + ((eq marker 'evil-jump-backward-swap) + (evil-set-jump) + (throw 'done nil)) + ((functionp marker) + (user-error "Cannot set special marker `%c'" char)) + ((evil-global-marker-p char) + (setq alist (default-value 'evil-markers-alist) + marker (make-marker)) + (evil-add-to-alist 'alist char marker) + (setq-default evil-markers-alist alist)) + (t + (setq marker (make-marker)) + (evil-add-to-alist 'evil-markers-alist char marker)))) + (add-hook 'kill-buffer-hook #'evil-swap-out-markers nil t) + (set-marker-insertion-type marker advance) + (set-marker marker (or pos (point)))))) + +(defun evil-get-marker (char &optional raw) + "Return the marker denoted by CHAR. +This is either a marker object as returned by `make-marker', +a number, a cons cell (FILE . POS) with FILE being a string +and POS a number, or nil. If RAW is non-nil, then the +return value may also be a variable, a movement function, +or a marker object pointing nowhere." + (let ((marker (if (evil-global-marker-p char) + (cdr-safe (assq char (default-value + 'evil-markers-alist))) + (cdr-safe (assq char evil-markers-alist))))) + (save-excursion + (if raw + marker + (when (and (symbolp marker) (boundp marker)) + (setq marker (symbol-value marker))) + (when (functionp marker) + (funcall marker) + (setq marker (point))) + (when (markerp marker) + (if (eq (marker-buffer marker) (current-buffer)) + (setq marker (marker-position marker)) + (setq marker (and (marker-buffer marker) marker)))) + (when (or (numberp marker) + (markerp marker) + (and (consp marker) + (stringp (car marker)) + (numberp (cdr marker)))) + marker))))) + +(defun evil-swap-out-markers () + "Turn markers into file references when the buffer is killed." + (and buffer-file-name + (dolist (entry evil-markers-alist) + (and (markerp (cdr entry)) + (eq (marker-buffer (cdr entry)) (current-buffer)) + (setcdr entry (cons buffer-file-name + (marker-position (cdr entry)))))))) +(put 'evil-swap-out-markers 'permanent-local-hook t) + +(defun evil-get-register (register &optional noerror) + "Return contents of REGISTER. +Signal an error if empty, unless NOERROR is non-nil. + +The following special registers are supported. + \" the unnamed register + * the clipboard contents + + the clipboard contents + <C-w> the word at point (ex mode only) + <C-a> the WORD at point (ex mode only) + <C-o> the symbol at point (ex mode only) + <C-f> the current file at point (ex mode only) + % the current file name (read only) + # the alternate file name (read only) + / the last search pattern (read only) + : the last command line (read only) + . the last inserted text (read only) + - the last small (less than a line) delete + _ the black hole register + = the expression register (read only)" + (condition-case err + (when (characterp register) + (or (cond + ((eq register ?\") + (current-kill 0)) + ((and (<= ?1 register) (<= register ?9)) + (let ((reg (- register ?1))) + (and (< reg (length kill-ring)) + (current-kill reg t)))) + ((memq register '(?* ?+)) + ;; the following code is modified from + ;; `x-selection-value-internal' + (let ((what (if (eq register ?*) 'PRIMARY 'CLIPBOARD)) + (request-type (or (and (boundp 'x-select-request-type) + x-select-request-type) + '(UTF8_STRING COMPOUNT_TEXT STRING))) + text) + (unless (consp request-type) + (setq request-type (list request-type))) + (while (and request-type (not text)) + (condition-case nil + (setq text (evil-get-selection what (pop request-type))) + (error nil))) + (when text + (remove-text-properties 0 (length text) '(foreign-selection nil) text)) + text)) + ((eq register ?\C-W) + (unless (evil-ex-p) + (user-error "Register <C-w> only available in ex state")) + (with-current-buffer evil-ex-current-buffer + (thing-at-point 'evil-word))) + ((eq register ?\C-A) + (unless (evil-ex-p) + (user-error "Register <C-a> only available in ex state")) + (with-current-buffer evil-ex-current-buffer + (thing-at-point 'evil-WORD))) + ((eq register ?\C-O) + (unless (evil-ex-p) + (user-error "Register <C-o> only available in ex state")) + (with-current-buffer evil-ex-current-buffer + (thing-at-point 'evil-symbol))) + ((eq register ?\C-F) + (unless (evil-ex-p) + (user-error "Register <C-f> only available in ex state")) + (with-current-buffer evil-ex-current-buffer + (thing-at-point 'filename))) + ((eq register ?%) + (or (buffer-file-name (and (evil-ex-p) + (minibufferp) + evil-ex-current-buffer)) + (user-error "No file name"))) + ((= register ?#) + (or (with-current-buffer (other-buffer) (buffer-file-name)) + (user-error "No file name"))) + ((eq register ?/) + (or (car-safe + (or (and (boundp 'evil-search-module) + (eq evil-search-module 'evil-search) + evil-ex-search-history) + (and isearch-regexp regexp-search-ring) + search-ring)) + (user-error "No previous regular expression"))) + ((eq register ?:) + (or (car-safe evil-ex-history) + (user-error "No previous command line"))) + ((eq register ?.) + evil-last-insertion) + ((eq register ?-) + evil-last-small-deletion) + ((eq register ?=) + (let* ((enable-recursive-minibuffers t) + (result (eval (car (read-from-string (read-string "=")))))) + (cond + ((or (stringp result) + (numberp result) + (symbolp result)) + (prin1-to-string result)) + ((sequencep result) + (mapconcat #'prin1-to-string result "\n")) + (t (user-error "Using %s as a string" (type-of result)))))) + ((eq register ?_) ; the black hole register + "") + (t + (setq register (downcase register)) + (get-register register))) + (user-error "Register `%c' is empty" register))) + (error (unless err (signal (car err) (cdr err)))))) + +(defun evil-set-register (register text) + "Set the contents of register REGISTER to TEXT. +If REGISTER is an upcase character then text is appended to that +register instead of replacing its content." + (cond + ((not (characterp register)) + (user-error "Invalid register")) + ;; don't allow modification of read-only registers + ((member register '(?: ?. ?%)) + (user-error "Can't modify read-only register")) + ((eq register ?\") + (kill-new text)) + ((and (<= ?1 register) (<= register ?9)) + (if (null kill-ring) + (kill-new text) + (let ((kill-ring-yank-pointer kill-ring-yank-pointer) + interprogram-paste-function + interprogram-cut-function) + (current-kill (- register ?1)) + (setcar kill-ring-yank-pointer text)))) + ((eq register ?*) + (evil-set-selection 'PRIMARY text)) + ((eq register ?+) + (evil-set-selection 'CLIPBOARD text)) + ((eq register ?-) + (setq evil-last-small-deletion text)) + ((eq register ?_) ; the black hole register + nil) + ((and (<= ?A register) (<= register ?Z)) + (setq register (downcase register)) + (let ((content (get-register register))) + (cond + ((not content) + (set-register register text)) + ((or (text-property-not-all 0 (length content) + 'yank-handler nil + content) + (text-property-not-all 0 (length text) + 'yank-handler nil + text)) + ;; some non-trivial yank-handler -> always switch to line handler + ;; ensure complete lines + (when (and (> (length content) 0) + (/= (aref content (1- (length content))) ?\n)) + (setq content (concat content "\n"))) + (when (and (> (length text) 0) + (/= (aref text (1- (length text))) ?\n)) + (setq text (concat text "\n"))) + (setq text (concat content text)) + (remove-list-of-text-properties 0 (length text) '(yank-handler) text) + (setq text (propertize text 'yank-handler '(evil-yank-line-handler))) + (set-register register text)) + (t + (set-register register (concat content text)))))) + (t + (set-register register text)))) + +(defun evil-register-list () + "Returns an alist of all registers" + (sort (append (mapcar #'(lambda (reg) + (cons reg (evil-get-register reg t))) + '(?\" ?* ?+ ?% ?# ?/ ?: ?. ?- + ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) + register-alist nil) + #'(lambda (reg1 reg2) (< (car reg1) (car reg2))))) + +(defsubst evil-kbd-macro-suppress-motion-error () + "Returns non-nil if a motion error should be suppressed. +Whether the motion error should be suppressed depends on the +variable `evil-kbd-macro-suppress-motion-error'." + (or (and defining-kbd-macro + (memq evil-kbd-macro-suppress-motion-error '(t record))) + (and executing-kbd-macro + (memq evil-kbd-macro-suppress-motion-error '(t replay))))) + +;;; Region + +;; `set-mark' does too much at once +(defun evil-move-mark (pos) + "Set buffer's mark to POS. +If POS is nil, delete the mark." + (when pos + (setq pos (evil-normalize-position pos))) + (set-marker (mark-marker) pos)) + +(defun evil-save-transient-mark-mode () + "Save Transient Mark mode and make it buffer-local. +Any changes to Transient Mark mode are now local to the current +buffer, until `evil-restore-transient-mark-mode' is called. + +Variables pertaining to Transient Mark mode are listed in +`evil-transient-vars', and their values are stored in +`evil-transient-vals'." + (dolist (var evil-transient-vars) + (when (and (boundp var) + (not (assq var evil-transient-vals))) + (push (list var (symbol-value var) + (local-variable-p var)) + evil-transient-vals) + (make-variable-buffer-local var) + (put var 'permanent-local t)))) + +(defun evil-restore-transient-mark-mode () + "Restore Transient Mark mode. +This presupposes that `evil-save-transient-mark-mode' has been +called earlier. If Transient Mark mode was disabled before but +enabled in the meantime, this function disables it; if it was +enabled before but disabled in the meantime, this function +enables it. + +The earlier settings of Transient Mark mode are stored in +`evil-transient-vals'." + (let (entry local var val) + (while (setq entry (pop evil-transient-vals)) + (setq var (pop entry) + val (pop entry) + local (pop entry)) + (unless local + (kill-local-variable var)) + (unless (equal (symbol-value var) val) + (if (fboundp var) + (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 +;; Mark mode, so we define our own toggle functions to make things +;; more predictable. +(defun evil-transient-mark (&optional arg) + "Toggle Transient Mark mode. +Ensure that the region is properly deactivated. +Enable with positive ARG, disable with negative ARG." + (unless (numberp arg) + (setq arg (if transient-mark-mode -1 1))) + (cond + ((< arg 1) + (evil-active-region -1) + ;; Transient Mark mode cannot be disabled + ;; while CUA mode is enabled + (when (fboundp 'cua-mode) + (cua-mode -1)) + (when transient-mark-mode + (transient-mark-mode -1))) + (t + (unless transient-mark-mode + (evil-active-region -1) + (transient-mark-mode 1))))) + +(defun evil-active-region (&optional arg) + "Toggle active region. +Ensure that Transient Mark mode is properly enabled. +Enable with positive ARG, disable with negative ARG." + (unless (numberp arg) + (setq arg (if (region-active-p) -1 1))) + (cond + ((and (< arg 1)) + (when (or transient-mark-mode mark-active) + (setq mark-active nil + deactivate-mark nil) + (when (boundp 'cua--explicit-region-start) + (setq cua--explicit-region-start nil)) + (run-hooks 'deactivate-mark-hook))) + (t + (evil-transient-mark 1) + (when deactivate-mark + (setq deactivate-mark nil)) + (unless (mark t) + (evil-move-mark (point))) + (unless (region-active-p) + (set-mark (mark t))) + (when (boundp 'cua--explicit-region-start) + (setq cua--explicit-region-start t))))) + +(defmacro evil-with-transient-mark-mode (&rest body) + "Execute BODY with Transient Mark mode. +Then restore Transient Mark mode to its previous setting." + (declare (indent defun) + (debug t)) + `(let ((inhibit-quit t) + evil-transient-vals) + (unwind-protect + (progn + (evil-save-transient-mark-mode) + (evil-transient-mark 1) + ,@body) + (evil-restore-transient-mark-mode)))) + +(defmacro evil-with-active-region (beg end &rest body) + "Execute BODY with an active region from BEG to END." + (declare (indent 2) + (debug t)) + `(let ((beg ,beg) (end ,end) + evil-transient-vals) + (evil-with-transient-mark-mode + (save-excursion + (evil-active-region 1) + (evil-move-mark beg) + (goto-char end) + ,@body)))) + +(defun evil-exchange-point-and-mark () + "Exchange point and mark without activating the region." + (let* ((point (point)) + (mark (or (mark t) point))) + (set-marker (mark-marker) point) + (goto-char mark))) + +(defun evil-apply-on-block (func beg end pass-columns &rest args) + "Call FUNC for each line of a block selection. +The selection is specified by the region BEG and END. FUNC must +take at least two arguments, the beginning and end of each +line. If PASS-COLUMNS is non-nil, these values are the columns, +otherwise tey are buffer positions. Extra arguments to FUNC may +be passed via ARGS." + (let ((eol-col (and (memq last-command '(next-line previous-line)) + (numberp temporary-goal-column) + temporary-goal-column)) + startcol startpt endcol endpt) + (save-excursion + (goto-char beg) + (setq startcol (current-column)) + (beginning-of-line) + (setq startpt (point)) + (goto-char end) + (setq endcol (current-column)) + (forward-line 1) + (setq endpt (point-marker)) + ;; ensure the start column is the left one. + (evil-sort startcol endcol) + ;; maybe find maximal column + (when eol-col + (setq eol-col 0) + (goto-char startpt) + (while (< (point) endpt) + (setq eol-col (max eol-col + (evil-column (line-end-position)))) + (forward-line 1)) + (setq endcol (max endcol + (min eol-col + (1+ (min (1- most-positive-fixnum) + (truncate temporary-goal-column))))))) + ;; start looping over lines + (goto-char startpt) + (while (< (point) endpt) + (if pass-columns + (apply func startcol endcol args) + (apply func + (save-excursion (evil-move-to-column startcol)) + (save-excursion (evil-move-to-column endcol t)) + args)) + (forward-line 1))))) + +(defun evil-apply-on-rectangle (function start end &rest args) + "Like `apply-on-rectangle' but maybe extends to eol. +If `temporary-goal-column' is set to a big number, then the +region of each line is extended to the end of each line. The end +column is set to the maximal column in all covered lines." + (apply #'evil-apply-on-block function start end t args)) + +;;; Insertion + +(defun evil-concat-ranges (ranges) + "Concatenate RANGES. +RANGES must be a list of ranges. They must be ordered so that +successive ranges share their boundaries. The return value is a +single range of disjoint union of the ranges or nil if the +disjoint union is not a single range." + (let ((range (car-safe ranges)) (ranges (cdr ranges)) r) + (while (and range (setq r (car-safe ranges))) + (setq range + (cond ((and (= (cdr r) (car range))) (cons (car r) (cdr range))) + ((and (= (cdr range) (car r))) (cons (car range) (cdr r))))) + (setq ranges (cdr ranges))) + range)) + +(defun evil-track-last-insertion (beg end len) + "Track the last insertion range and its text. +The insertion range is stored as a pair of buffer positions in +`evil-current-insertion'. If a subsequent change is compatible, +then the current range is modified, otherwise it is replaced by a +new range. Compatible changes are changes that do not create a +disjoin range." + ;; deletion + (when (> len 0) + (if (and evil-current-insertion + (>= beg (car evil-current-insertion)) + (<= (+ beg len) (cdr evil-current-insertion))) + (setcdr evil-current-insertion + (- (cdr evil-current-insertion) len)) + (setq evil-current-insertion nil))) + ;; insertion + (if (and evil-current-insertion + (>= beg (car evil-current-insertion)) + (<= beg (cdr evil-current-insertion))) + (setcdr evil-current-insertion + (+ (- end beg) + (cdr evil-current-insertion))) + (setq evil-current-insertion (cons beg end)))) +(put 'evil-track-last-insertion 'permanent-local-hook t) + +(defun evil-start-track-last-insertion () + "Start tracking the last insertion." + (setq evil-current-insertion nil) + (add-hook 'after-change-functions #'evil-track-last-insertion nil t)) + +(defun evil-stop-track-last-insertion () + "Stop tracking the last insertion. +The tracked insertion is set to `evil-last-insertion'." + (setq evil-last-insertion + (and evil-current-insertion + ;; Check whether the insertion range is a valid buffer + ;; range. If a buffer modification is done from within + ;; another change hook or modification-hook (yasnippet + ;; does this using overlay modification-hooks), then the + ;; insertion information may be invalid. There is no way + ;; to detect this situation, but at least we should + ;; ensure that no error occurs (see bug #272). + (>= (car evil-current-insertion) (point-min)) + (<= (cdr evil-current-insertion) (point-max)) + (buffer-substring-no-properties (car evil-current-insertion) + (cdr evil-current-insertion)))) + (remove-hook 'after-change-functions #'evil-track-last-insertion t)) + +;;; Paste + +(defun evil-yank-characters (beg end &optional register yank-handler) + "Saves the characters defined by the region BEG and END in the kill-ring." + (let ((text (filter-buffer-substring beg end))) + (when yank-handler + (setq text (propertize text 'yank-handler (list yank-handler)))) + (when register + (evil-set-register register text)) + (when evil-was-yanked-without-register + (evil-set-register ?0 text)) ; "0 register contains last yanked text + (unless (eq register ?_) + (kill-new text)))) + +(defun evil-yank-lines (beg end &optional register yank-handler) + "Saves the lines in the region BEG and END into the kill-ring." + (let* ((text (filter-buffer-substring beg end)) + (yank-handler (list (or yank-handler + #'evil-yank-line-handler) + nil + t))) + ;; Ensure the text ends with a newline. This is required + ;; if the deleted lines were the last lines in the buffer. + (when (or (zerop (length text)) + (/= (aref text (1- (length text))) ?\n)) + (setq text (concat text "\n"))) + (setq text (propertize text 'yank-handler yank-handler)) + (when register + (evil-set-register register text)) + (when evil-was-yanked-without-register + (evil-set-register ?0 text)) ; "0 register contains last yanked text + (unless (eq register ?_) + (kill-new text)))) + +(defun evil-yank-rectangle (beg end &optional register yank-handler) + "Saves the rectangle defined by region BEG and END into the kill-ring." + (let ((lines (list nil))) + (evil-apply-on-rectangle #'extract-rectangle-line beg end lines) + ;; We remove spaces from the beginning and the end of the next. + ;; Spaces are inserted explicitly in the yank-handler in order to + ;; NOT insert lines full of spaces. + (setq lines (nreverse (cdr lines))) + ;; `text' is used as default insert text when pasting this rectangle + ;; in another program, e.g., using the X clipboard. + (let* ((yank-handler (list (or yank-handler + #'evil-yank-block-handler) + lines + t + 'evil-delete-yanked-rectangle)) + (text (propertize (mapconcat #'identity lines "\n") + 'yank-handler yank-handler))) + (when register + (evil-set-register register text)) + (when evil-was-yanked-without-register + (evil-set-register ?0 text)) ; "0 register contains last yanked text + (unless (eq register ?_) + (kill-new text))))) + +(defun evil-remove-yank-excluded-properties (text) + "Removes `yank-excluded-properties' from TEXT." + (if (eq yank-excluded-properties t) + (set-text-properties 0 (length text) nil text) + (remove-list-of-text-properties 0 (length text) + yank-excluded-properties text))) + +(defun evil-yank-line-handler (text) + "Inserts the current text linewise." + (let ((text (apply #'concat (make-list (or evil-paste-count 1) text))) + (opoint (point))) + (evil-remove-yank-excluded-properties text) + (cond + ((eq this-command 'evil-paste-before) + (evil-move-beginning-of-line) + (evil-move-mark (point)) + (insert text) + (setq evil-last-paste + (list 'evil-paste-before + evil-paste-count + opoint + (mark t) + (point))) + (evil-set-marker ?\[ (mark)) + (evil-set-marker ?\] (1- (point))) + (evil-exchange-point-and-mark) + (back-to-indentation)) + ((eq this-command 'evil-paste-after) + (evil-move-end-of-line) + (evil-move-mark (point)) + (insert "\n") + (insert text) + (evil-set-marker ?\[ (1+ (mark))) + (evil-set-marker ?\] (1- (point))) + (delete-char -1) ; delete the last newline + (setq evil-last-paste + (list 'evil-paste-after + evil-paste-count + opoint + (mark t) + (point))) + (evil-move-mark (1+ (mark t))) + (evil-exchange-point-and-mark) + (back-to-indentation)) + (t + (insert text))))) + +(defun evil-yank-block-handler (lines) + "Inserts the current text as block." + (let ((count (or evil-paste-count 1)) + (col (if (eq this-command 'evil-paste-after) + (1+ (current-column)) + (current-column))) + (current-line (line-number-at-pos (point))) + (opoint (point)) + epoint) + (dolist (line lines) + ;; concat multiple copies according to count + (setq line (apply #'concat (make-list count line))) + ;; strip whitespaces at beginning and end + (string-match "^ *\\(.*?\\) *$" line) + (let ((text (match-string 1 line)) + (begextra (match-beginning 1)) + (endextra (- (match-end 0) (match-end 1)))) + ;; maybe we have to insert a new line at eob + (while (< (line-number-at-pos (point)) + current-line) + (goto-char (point-max)) + (insert "\n")) + (setq current-line (1+ current-line)) + ;; insert text unless we insert an empty line behind eol + (unless (and (< (evil-column (line-end-position)) col) + (zerop (length text))) + ;; if we paste behind eol, it may be sufficient to insert tabs + (if (< (evil-column (line-end-position)) col) + (move-to-column (+ col begextra) t) + (move-to-column col t) + (insert (make-string begextra ?\s))) + (evil-remove-yank-excluded-properties text) + (insert text) + (unless (eolp) + ;; text follows, so we have to insert spaces + (insert (make-string endextra ?\s))) + (setq epoint (point))) + (forward-line 1))) + (setq evil-last-paste + (list this-command + evil-paste-count + opoint + (length lines) ; number of rows + (* count (length (car lines))))) ; number of colums + (evil-set-marker ?\[ opoint) + (evil-set-marker ?\] (1- epoint)) + (goto-char opoint) + (when (and (eq this-command 'evil-paste-after) + (not (eolp))) + (forward-char)))) + +(defun evil-delete-yanked-rectangle (nrows ncols) + "Special function to delete the block yanked by a previous paste command." + (let ((opoint (point)) + (col (if (eq last-command 'evil-paste-after) + (1+ (current-column)) + (current-column)))) + (dotimes (i nrows) + (delete-region (save-excursion + (move-to-column col) + (point)) + (save-excursion + (move-to-column (+ col ncols)) + (point))) + (unless (eobp) (forward-line))) + (goto-char opoint))) + +;; TODO: if undoing is disabled in the current buffer, paste-pop won't +;; work. Although this is probably not a big problem, because usually +;; buffers where `evil-paste-pop' may be useful have undoing enabled. +;; A solution would be to temporarily enable undo when pasting and +;; store the undo information in a special variable that does not +;; interfere with `buffer-undo-list'. +(defun evil-paste-pop (count) + "Replace the just-yanked stretch of killed text with a different stretch. +This command is allowed only immediatly after a `yank', +`evil-paste-before', `evil-paste-after' or `evil-paste-pop'. +This command uses the same paste command as before, i.e., when +used after `evil-paste-after' the new text is also yanked using +`evil-paste-after', used with the same paste-count argument. + +The COUNT argument inserts the COUNTth previous kill. If COUNT +is negative this is a more recent kill." + (interactive "p") + (unless (memq last-command + '(evil-paste-after + evil-paste-before + evil-visual-paste)) + (user-error "Previous command was not an evil-paste: %s" last-command)) + (unless evil-last-paste + (user-error "Previous paste command used a register")) + (evil-undo-pop) + (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 + (let ((kill-ring (list (current-kill + (if (and (> count 0) (nth 5 evil-last-paste)) + ;; if was visual paste then skip the + ;; text that has been replaced + (1+ count) + count)))) + (kill-ring-yank-pointer kill-ring)) + (when (eq last-command 'evil-visual-paste) + (let ((evil-no-display t)) + (evil-visual-restore))) + (funcall (nth 0 evil-last-paste) (nth 1 evil-last-paste)) + ;; if this was a visual paste, then mark the last paste as NOT + ;; being the first visual paste + (when (eq last-command 'evil-visual-paste) + (setcdr (nthcdr 4 evil-last-paste) nil)))) + +(defun evil-paste-pop-next (count) + "Same as `evil-paste-pop' but with negative argument." + (interactive "p") + (evil-paste-pop (- count))) + +;;; Interactive forms + +(defun evil-match-interactive-code (interactive &optional pos) + "Match an interactive code at position POS in string INTERACTIVE. +Returns the first matching entry in `evil-interactive-alist', or nil." + (let ((length (length interactive)) + (pos (or pos 0))) + (catch 'done + (dolist (entry evil-interactive-alist) + (let* ((string (car entry)) + (end (+ (length string) pos))) + (when (and (<= end length) + (string= string + (substring interactive pos end))) + (throw 'done entry))))))) + +(defun evil-concatenate-interactive-forms (&rest forms) + "Concatenate interactive list expressions FORMS. +Returns a single expression where successive expressions +are joined, if possible." + (let (result) + (when forms + (while (cdr forms) + (cond + ((null (car forms)) + (pop forms)) + ((and (eq (car (car forms)) 'list) + (eq (car (cadr forms)) 'list)) + (setq forms (cons (append (car forms) + (cdr (cadr forms))) + (cdr (cdr forms))))) + (t + (push (pop forms) result)))) + (when (car forms) + (push (pop forms) result)) + (setq result (nreverse result)) + (cond + ((null result)) + ((null (cdr result)) + (car result)) + (t + `(append ,@result)))))) + +(defun evil-interactive-string (string) + "Evaluate the interactive string STRING. +The string may contain extended interactive syntax. +The return value is a cons cell (FORM . PROPERTIES), +where FORM is a single list-expression to be passed to +a standard `interactive' statement, and PROPERTIES is a +list of command properties as passed to `evil-define-command'." + (let ((length (length string)) + (pos 0) + code expr forms match plist prompt properties) + (while (< pos length) + (if (eq (aref string pos) ?\n) + (setq pos (1+ pos)) + (setq match (evil-match-interactive-code string pos)) + (if (null match) + (user-error "Unknown interactive code: `%s'" + (substring string pos)) + (setq code (car match) + expr (car (cdr match)) + plist (cdr (cdr match)) + pos (+ pos (length code))) + (when (functionp expr) + (setq prompt + (substring string pos + (or (string-match "\n" string pos) + length)) + pos (+ pos (length prompt)) + expr `(funcall ,expr ,prompt))) + (setq forms (append forms (list expr)) + properties (append properties plist))))) + (cons `(append ,@forms) properties))) + +(defun evil-interactive-form (&rest args) + "Evaluate interactive forms ARGS. +The return value is a cons cell (FORM . PROPERTIES), +where FORM is a single list-expression to be passed to +a standard `interactive' statement, and PROPERTIES is a +list of command properties as passed to `evil-define-command'." + (let (forms properties) + (dolist (arg args) + (if (not (stringp arg)) + (setq forms (append forms (list arg))) + (setq arg (evil-interactive-string arg) + forms (append forms (cdr (car arg))) + properties (append properties (cdr arg))))) + (cons (apply #'evil-concatenate-interactive-forms forms) + properties))) + +;;; Types + +(defun evil-type (object &optional default) + "Return the type of OBJECT, or DEFAULT if none." + (let (type) + (cond + ((overlayp object) + (setq type (overlay-get object :type))) + ((evil-range-p object) + (setq type (nth 2 object))) + ((listp object) + (setq type (plist-get object :type))) + ((commandp object) + (setq type (evil-get-command-property object :type))) + ((symbolp object) + (setq type (get object 'type)))) + (setq type (or type default)) + (and (evil-type-p type) type))) + +(defun evil-set-type (object type) + "Set the type of OBJECT to TYPE. +For example, (evil-set-type 'next-line 'line) +will make `line' the type of the `next-line' command." + (cond + ((overlayp object) + (overlay-put object :type type)) + ((evil-range-p object) + (evil-set-range-type object type)) + ((listp object) + (plist-put object :type type)) + ((commandp object) + (evil-set-command-property object :type type)) + ((symbolp object) + (put object 'type type))) + object) + +(defun evil-type-property (type prop) + "Return property PROP for TYPE." + (evil-get-property evil-type-properties type prop)) + +(defun evil-type-p (sym) + "Whether SYM is the name of a type." + (assq sym evil-type-properties)) + +(defun evil-expand (beg end type &rest properties) + "Expand BEG and END as TYPE with PROPERTIES. +Returns a list (BEG END TYPE PROPERTIES ...), where the tail +may contain a property list." + (apply #'evil-transform + ;; don't expand if already expanded + (unless (plist-get properties :expanded) :expand) + beg end type properties)) + +(defun evil-contract (beg end type &rest properties) + "Contract BEG and END as TYPE with PROPERTIES. +Returns a list (BEG END TYPE PROPERTIES ...), where the tail +may contain a property list." + (apply #'evil-transform :contract beg end type properties)) + +(defun evil-normalize (beg end type &rest properties) + "Normalize BEG and END as TYPE with PROPERTIES. +Returns a list (BEG END TYPE PROPERTIES ...), where the tail +may contain a property list." + (apply #'evil-transform :normalize beg end type properties)) + +(defun evil-transform (transform beg end type &rest properties) + "Apply TRANSFORM on BEG and END with PROPERTIES. +Returns a list (BEG END TYPE PROPERTIES ...), where the tail +may contain a property list. If TRANSFORM is undefined, +return positions unchanged." + (let* ((type (or type (evil-type properties))) + (transform (when (and type transform) + (evil-type-property type transform)))) + (if transform + (apply transform beg end properties) + (apply #'evil-range beg end type properties)))) + +(defun evil-describe (beg end type &rest properties) + "Return description of BEG and END with PROPERTIES. +If no description is available, return the empty string." + (let* ((type (or type (evil-type properties))) + (properties (plist-put properties :type type)) + (describe (evil-type-property type :string))) + (or (when describe + (apply describe beg end properties)) + ""))) + +;;; Ranges + +(defun evil-range (beg end &optional type &rest properties) + "Return a list (BEG END [TYPE] PROPERTIES...). +BEG and END are buffer positions (numbers or markers), +TYPE is a type as per `evil-type-p', and PROPERTIES is +a property list." + (let ((beg (evil-normalize-position beg)) + (end (evil-normalize-position end))) + (when (and (numberp beg) (numberp end)) + (append (list (min beg end) (max beg end)) + (when (evil-type-p type) + (list type)) + properties)))) + +(defun evil-range-p (object) + "Whether OBJECT is a range." + (and (listp object) + (>= (length object) 2) + (numberp (nth 0 object)) + (numberp (nth 1 object)))) + +(defun evil-range-beginning (range) + "Return beginning of RANGE." + (when (evil-range-p range) + (let ((beg (evil-normalize-position (nth 0 range))) + (end (evil-normalize-position (nth 1 range)))) + (min beg end)))) + +(defun evil-range-end (range) + "Return end of RANGE." + (when (evil-range-p range) + (let ((beg (evil-normalize-position (nth 0 range))) + (end (evil-normalize-position (nth 1 range)))) + (max beg end)))) + +(defun evil-range-properties (range) + "Return properties of RANGE." + (when (evil-range-p range) + (if (evil-type range) + (nthcdr 3 range) + (nthcdr 2 range)))) + +(defun evil-copy-range (range) + "Return a copy of RANGE." + (copy-sequence range)) + +(defun evil-set-range (range &optional beg end type &rest properties) + "Set RANGE to have beginning BEG and end END. +The TYPE and additional PROPERTIES may also be specified. +If an argument is nil, it's not used; the previous value is retained. +See also `evil-set-range-beginning', `evil-set-range-end', +`evil-set-range-type' and `evil-set-range-properties'." + (when (evil-range-p range) + (let ((beg (or (evil-normalize-position beg) + (evil-range-beginning range))) + (end (or (evil-normalize-position end) + (evil-range-end range))) + (type (or type (evil-type range))) + (plist (evil-range-properties range))) + (evil-sort beg end) + (setq plist (evil-concat-plists plist properties)) + (evil-set-range-beginning range beg) + (evil-set-range-end range end) + (evil-set-range-type range type) + (evil-set-range-properties range plist) + range))) + +(defun evil-set-range-beginning (range beg &optional copy) + "Set RANGE's beginning to BEG. +If COPY is non-nil, return a copy of RANGE." + (when copy + (setq range (evil-copy-range range))) + (setcar range beg) + range) + +(defun evil-set-range-end (range end &optional copy) + "Set RANGE's end to END. +If COPY is non-nil, return a copy of RANGE." + (when copy + (setq range (evil-copy-range range))) + (setcar (cdr range) end) + range) + +(defun evil-set-range-type (range type &optional copy) + "Set RANGE's type to TYPE. +If COPY is non-nil, return a copy of RANGE." + (when copy + (setq range (evil-copy-range range))) + (if type + (setcdr (cdr range) + (cons type (evil-range-properties range))) + (setcdr (cdr range) (evil-range-properties range))) + range) + +(defun evil-set-range-properties (range properties &optional copy) + "Set RANGE's properties to PROPERTIES. +If COPY is non-nil, return a copy of RANGE." + (when copy + (setq range (evil-copy-range range))) + (if (evil-type range) + (setcdr (cdr (cdr range)) properties) + (setcdr (cdr range) properties)) + range) + +(defun evil-range-union (range1 range2 &optional type) + "Return the union of the ranges RANGE1 and RANGE2. +If the ranges have conflicting types, use RANGE1's type. +This can be overridden with TYPE." + (when (and (evil-range-p range1) + (evil-range-p range2)) + (evil-range (min (evil-range-beginning range1) + (evil-range-beginning range2)) + (max (evil-range-end range1) + (evil-range-end range2)) + (or type + (evil-type range1) + (evil-type range2))))) + +(defun evil-subrange-p (range1 range2) + "Whether RANGE1 is contained within RANGE2." + (and (evil-range-p range1) + (evil-range-p range2) + (<= (evil-range-beginning range2) + (evil-range-beginning range1)) + (>= (evil-range-end range2) + (evil-range-end range1)))) + +(defun evil-select-inner-object (thing beg end type &optional count line) + "Return an inner text object range of COUNT objects. +If COUNT is positive, return objects following point; if COUNT is +negative, return objects preceding point. If one is unspecified, +the other is used with a negative argument. THING is a symbol +understood by thing-at-point. BEG, END and TYPE specify the +current selection. If LINE is non-nil, the text object should be +linewise, otherwise it is character wise." + (let* ((count (or count 1)) + (bnd (or (let ((b (bounds-of-thing-at-point thing))) + (and b (< (point) (cdr b)) b)) + (evil-bounds-of-not-thing-at-point thing)))) + ;; check if current object is selected + (when (or (not beg) (not end) + (> beg (car bnd)) + (< end (cdr bnd)) + (and (eq type 'inclusive) + (= (1+ beg) end))) ; empty region does not count + (when (or (not beg) (< (car bnd) beg)) (setq beg (car bnd))) + (when (or (not end) (> (cdr bnd) end)) (setq end (cdr bnd))) + (setq count (if (> count 0) (1- count) (1+ count)))) + (goto-char (if (< count 0) beg end)) + (evil-forward-nearest count + #'(lambda (cnt) (forward-thing thing cnt)) + #'(lambda (cnt) (evil-forward-not-thing thing cnt))) + (evil-range (if (>= count 0) beg (point)) + (if (< count 0) end (point)) + (if line 'line type) + :expanded t))) + +(defun evil-select-an-object (thing beg end type count &optional line) + "Return an outer text object range of COUNT objects. +If COUNT is positive, return objects following point; if COUNT is +negative, return objects preceding point. If one is unspecified, +the other is used with a negative argument. THING is a symbol +understood by thing-at-point. BEG, END and TYPE specify the +current selection. If LINE is non-nil, the text object should be +linewise, otherwise it is character wise." + (let* ((dir (if (> (or count 1) 0) +1 -1)) + (count (abs (or count 1))) + (objbnd (let ((b (bounds-of-thing-at-point thing))) + (and b (< (point) (cdr b)) b))) + (bnd (or objbnd (evil-bounds-of-not-thing-at-point thing))) + addcurrent other) + ;; check if current object is not selected + (when (or (not beg) (not end) + (> beg (car bnd)) + (< end (cdr bnd)) + (and (eq type 'inclusive) + (= (1+ beg) end))) ; empty region does not count + ;; if not, enlarge selection + (when (or (not beg) (< (car bnd) beg)) (setq beg (car bnd))) + (when (or (not end) (> (cdr bnd) end)) (setq end (cdr bnd))) + (if objbnd (setq addcurrent t))) + ;; make other and (point) reflect the selection + (cond + ((> dir 0) (goto-char end) (setq other beg)) + (t (goto-char beg) (setq other end))) + (cond + ;; do nothing more than only current is selected + ((not (and (= beg (car bnd)) (= end (cdr bnd))))) + ;; current match is thing, add whitespace + (objbnd + (let ((wsend (evil-with-restriction + ;; restrict to current line if we do non-line selection + (and (not line) (line-beginning-position)) + (and (not line) (line-end-position)) + (evil-bounds-of-not-thing-at-point thing dir)))) + (cond + (wsend + ;; add whitespace at end + (goto-char wsend) + (setq addcurrent t)) + (t + ;; no whitespace at end, try beginning + (save-excursion + (goto-char other) + (setq wsend + (evil-with-restriction + ;; restrict to current line if we do non-line selection + (and (not line) (line-beginning-position)) + (and (not line) (line-end-position)) + (evil-bounds-of-not-thing-at-point thing (- dir)))) + (when wsend (setq other wsend addcurrent t))))))) + ;; current match is whitespace, add thing + (t + (forward-thing thing dir) + (setq addcurrent t))) + ;; possibly count current object as selection + (if addcurrent (setq count (1- count))) + ;; move + (dotimes (var count) + (let ((wsend (evil-bounds-of-not-thing-at-point thing dir))) + (if (and wsend (/= wsend (point))) + ;; start with whitespace + (forward-thing thing dir) + ;; start with thing + (forward-thing thing dir) + (setq wsend (evil-bounds-of-not-thing-at-point thing dir)) + (when wsend (goto-char wsend))))) + ;; return range + (evil-range (if (> dir 0) other (point)) + (if (< dir 0) other (point)) + (if line 'line type) + :expanded t))) + +(defun evil--get-block-range (op cl selection-type) + "Return the exclusive range of a visual selection. +OP and CL are pairs of buffer positions for the opening and +closing delimiter of a range. SELECTION-TYPE is the desired type +of selection. It is a symbol that determines which parts of the +block are selected. If it is 'inclusive or t the returned range +is \(cons (car OP) (cdr CL)). If it is 'exclusive or nil the +returned range is (cons (cdr OP) (car CL)). If it is +'exclusive-line the returned range will skip whitespace at the +end of the line of OP and at the beginning of the line of CL." + (cond + ((memq selection-type '(inclusive t)) (cons (car op) (cdr cl))) + ((memq selection-type '(exclusive nil)) (cons (cdr op) (car cl))) + ((eq selection-type 'exclusive-line) + (let ((beg (cdr op)) + (end (car cl))) + (save-excursion + (goto-char beg) + (when (and (eolp) (not (eobp))) + (setq beg (line-beginning-position 2))) + (goto-char end) + (skip-chars-backward " \t") + (when (bolp) + (setq end (point)) + (goto-char beg) + (when (and (not (bolp)) (< beg end)) + (setq end (1- end))))) + (cons beg end))) + (t + (user-error "Unknown selection-type %s" selection-type)))) + +(defun evil-select-block (thing beg end type count + &optional + selection-type + countcurrent + fixedscan) + "Return a range (BEG END) of COUNT delimited text objects. +BEG END TYPE are the currently selected (visual) range. The +delimited object must be given by THING-up function (see +`evil-up-block'). + +SELECTION-TYPE is symbol that determines which parts of the block +are selected. If it is 'inclusive or t OPEN and CLOSE are +included in the range. If it is 'exclusive or nil the delimiters +are not contained. If it is 'exclusive-line the delimiters are +not included as well as adjacent whitespace until the beginning +of the next line or the end of the previous line. If the +resulting selection consists of complete lines only and visual +state is not active, the returned selection is linewise. + +If COUNTCURRENT is non-nil an objected is counted if the current +selection matches that object exactly. + +Usually scanning for the surrounding block starts at (1+ beg) +and (1- end). If this might fail due to the behavior of THING +then FIXEDSCAN can be set to t. In this case the scan starts at +BEG and END. One example where this might fail is if BEG and END +are the delimiters of a string or comment." + (save-excursion + (save-match-data + (let* ((orig-beg beg) + (orig-end end) + (beg (or beg (point))) + (end (or end (point))) + (count (abs (or count 1))) + op cl op-end cl-end) + ;; We always assume at least one selected character. + (if (= beg end) (setq end (1+ end))) + ;; We scan twice: starting at (1+ beg) forward and at (1- end) + ;; backward. The resulting selection is the smaller one. + (goto-char (if fixedscan beg (1+ beg))) + (when (and (zerop (funcall thing +1)) (match-beginning 0)) + (setq cl (cons (match-beginning 0) (match-end 0))) + (goto-char (car cl)) + (when (and (zerop (funcall thing -1)) (match-beginning 0)) + (setq op (cons (match-beginning 0) (match-end 0))))) + ;; start scanning from end + (goto-char (if fixedscan end (1- end))) + (when (and (zerop (funcall thing -1)) (match-beginning 0)) + (setq op-end (cons (match-beginning 0) (match-end 0))) + (goto-char (cdr op-end)) + (when (and (zerop (funcall thing +1)) (match-beginning 0)) + (setq cl-end (cons (match-beginning 0) (match-end 0))))) + ;; Bug #607: use the tightest selection that contains the + ;; original selection. If non selection contains the original, + ;; use the larger one. + (cond + ((and (not op) (not cl-end)) + (error "No surrounding delimiters found")) + ((or (not op) ; first not found + (and cl-end ; second found + (>= (car op-end) (car op)) ; second smaller + (<= (cdr cl-end) (cdr cl)) + (<= (car op-end) beg) ; second contains orig + (>= (cdr cl-end) end))) + (setq op op-end cl cl-end))) + (setq op-end op cl-end cl) ; store copy + ;; if the current selection contains the surrounding + ;; delimiters, they do not count as new selection + (let ((cnt (if (and orig-beg orig-end (not countcurrent)) + (let ((sel (evil--get-block-range op cl selection-type))) + (if (and (<= orig-beg (car sel)) + (>= orig-end (cdr sel))) + count + (1- count))) + (1- count)))) + ;; starting from the innermost surrounding delimiters + ;; increase selection + (when (> cnt 0) + (setq op (progn + (goto-char (car op-end)) + (funcall thing (- cnt)) + (if (match-beginning 0) + (cons (match-beginning 0) (match-end 0)) + op)) + cl (progn + (goto-char (cdr cl-end)) + (funcall thing cnt) + (if (match-beginning 0) + (cons (match-beginning 0) (match-end 0)) + cl))))) + (let ((sel (evil--get-block-range op cl selection-type))) + (setq op (car sel) + cl (cdr sel))) + (cond + ((and (equal op orig-beg) (equal cl orig-end) + (or (not countcurrent) + (and countcurrent (/= count 1)))) + (error "No surrounding delimiters found")) + ((save-excursion + (and (not (evil-visual-state-p)) + (eq type 'inclusive) + (progn (goto-char op) (bolp)) + (progn (goto-char cl) (bolp)))) + (evil-range op cl 'line :expanded t)) + (t + (evil-range op cl type :expanded t))))))) + +(defun evil-select-paren (open close beg end type count &optional inclusive) + "Return a range (BEG END) of COUNT delimited text objects. +OPEN and CLOSE specify the opening and closing delimiter, +respectively. BEG END TYPE are the currently selected (visual) +range. If INCLUSIVE is non-nil, OPEN and CLOSE are included in +the range; otherwise they are excluded. + +The types of OPEN and CLOSE specify which kind of THING is used +for parsing with `evil-select-block'. If OPEN and CLOSE are +characters `evil-up-paren' is used. Otherwise OPEN and CLOSE +must be regular expressions and `evil-up-block' is used. + +If the selection is exclusive, whitespace at the end or at the +beginning of the selection until the end-of-line or beginning-of-line +is ignored." + ;; we need special linewise exclusive selection + (unless inclusive (setq inclusive 'exclusive-line)) + (cond + ((and (characterp open) (characterp close)) + (let ((thing #'(lambda (&optional cnt) + (evil-up-paren open close cnt))) + (bnd (or (bounds-of-thing-at-point 'evil-string) + (bounds-of-thing-at-point 'evil-comment) + ;; If point is at the opening quote of a string, + ;; this must be handled as if point is within the + ;; string, i.e. the selection must be extended + ;; around the string. Otherwise + ;; `evil-select-block' might do the wrong thing + ;; because it accidentally moves point inside the + ;; string (for inclusive selection) when looking + ;; for the current surrounding block. (re #364) + (and (= (point) (or beg (point))) + (save-excursion + (goto-char (1+ (or beg (point)))) + (or (bounds-of-thing-at-point 'evil-string) + (bounds-of-thing-at-point 'evil-comment))))))) + (if (not bnd) + (evil-select-block thing beg end type count inclusive) + (or (evil-with-restriction (car bnd) (cdr bnd) + (condition-case nil + (evil-select-block thing beg end type count inclusive) + (error nil))) + (save-excursion + (setq beg (or beg (point)) + end (or end (point))) + (goto-char (car bnd)) + (let ((extbeg (min beg (car bnd))) + (extend (max end (cdr bnd)))) + (evil-select-block thing + extbeg extend + type + count + inclusive + (or (< extbeg beg) (> extend end)) + t))))))) + (t + (evil-select-block #'(lambda (&optional cnt) + (evil-up-block open close cnt)) + beg end type count inclusive)))) + +(defun evil-select-quote-thing (thing beg end type count &optional inclusive) + "Selection THING as if it described a quoted object. +THING is typically either 'evil-quote or 'evil-chars. This +function is called from `evil-select-quote'." + (save-excursion + (let* ((count (or count 1)) + (dir (if (> count 0) 1 -1)) + (bnd (let ((b (bounds-of-thing-at-point thing))) + (and b (< (point) (cdr b)) b))) + contains-string + addcurrent + wsboth) + (if inclusive (setq inclusive t) + (when (= (abs count) 2) + (setq count dir) + (setq inclusive 'quote-only)) + ;; never extend with exclusive selection + (setq beg nil end nil)) + ;; check if the previously selected range does not contain a + ;; string + (unless (and beg end + (save-excursion + (goto-char (if (> dir 0) beg end)) + (forward-thing thing dir) + (and (<= beg (point)) (< (point) end)))) + ;; if so forget the range + (setq beg nil end nil)) + ;; check if there is a current object, if not fetch one + (when (not bnd) + (unless (and (zerop (forward-thing thing dir)) + (setq bnd (bounds-of-thing-at-point thing))) + (error "No quoted string found")) + (if (> dir 0) + (setq end (point)) + (setq beg (point))) + (setq addcurrent t)) + ;; check if current object is not selected + (when (or (not beg) (not end) (> beg (car bnd)) (< end (cdr bnd))) + ;; if not, enlarge selection + (when (or (not beg) (< (car bnd) beg)) (setq beg (car bnd))) + (when (or (not end) (> (cdr bnd) end)) (setq end (cdr bnd))) + (setq addcurrent t wsboth t)) + ;; maybe count current element + (when addcurrent + (setq count (if (> dir 0) (1- count) (1+ count)))) + ;; enlarge selection + (goto-char (if (> dir 0) end beg)) + (when (and (not addcurrent) + (= count (forward-thing thing count))) + (error "No quoted string found")) + (if (> dir 0) (setq end (point)) (setq beg (point))) + ;; add whitespace + (cond + ((not inclusive) (setq beg (1+ beg) end (1- end))) + ((not (eq inclusive 'quote-only)) + ;; try to add whitespace in forward direction + (goto-char (if (> dir 0) end beg)) + (if (setq bnd (bounds-of-thing-at-point 'evil-space)) + (if (> dir 0) (setq end (cdr bnd)) (setq beg (car bnd))) + ;; if not found try backward direction + (goto-char (if (> dir 0) beg end)) + (if (and wsboth (setq bnd (bounds-of-thing-at-point 'evil-space))) + (if (> dir 0) (setq beg (car bnd)) (setq end (cdr bnd))))))) + (evil-range beg end + ;; HACK: fixes #583 + ;; When not in visual state, an empty range is + ;; possible. However, this cannot be achieved with + ;; inclusive ranges, hence we use exclusive ranges + ;; in this case. In visual state the range must be + ;; inclusive because otherwise the selection would + ;; be wrong. + (if (evil-visual-state-p) 'inclusive 'exclusive) + :expanded t)))) + +(defun evil-select-quote (quote beg end type count &optional inclusive) + "Return a range (BEG END) of COUNT quoted text objects. +QUOTE specifies the quotation delimiter. BEG END TYPE are the +currently selected (visual) range. + +If INCLUSIVE is nil the previous selection is ignore. If there is +quoted string at point this object will be selected, otherwise +the following (if (> COUNT 0)) or preceeding object (if (< COUNT +0)) is selected. If (/= (abs COUNT) 2) the delimiting quotes are not +contained in the range, otherwise they are contained in the range. + +If INCLUSIVE is non-nil the selection depends on the previous +selection. If the currently selection contains at least one +character that is contained in a quoted string then the selection +is extended, otherwise it is thrown away. If there is a +non-selected object at point then this object is added to the +selection. Otherwise the selection is extended to the +following (if (> COUNT 0)) or preceeding object (if (< COUNT +0)). Any whitespace following (or preceeding if (< COUNT 0)) the +new selection is added to the selection. If no such whitespace +exists and the selection contains only one quoted string then the +preceeding (or following) whitespace is added to the range. " + (let ((evil-forward-quote-char quote)) + (or (let ((bnd (or (bounds-of-thing-at-point 'evil-comment) + (bounds-of-thing-at-point 'evil-string)))) + (when (and bnd (< (point) (cdr bnd)) + (/= (char-after (car bnd)) quote) + (/= (char-before (cdr bnd)) quote)) + (evil-with-restriction (car bnd) (cdr bnd) + (condition-case nil + (evil-select-quote-thing 'evil-quote-simple + beg end type + count + inclusive) + (error nil))))) + (let ((evil-forward-quote-char quote)) + (evil-select-quote-thing 'evil-quote + beg end type + count + inclusive))))) + +(defun evil-select-xml-tag (beg end type &optional count inclusive) + "Return a range (BEG END) of COUNT matching XML tags. +If INCLUSIVE is non-nil, the tags themselves are included +from the range." + (cond + ((and (not inclusive) (= (abs (or count 1)) 1)) + (let ((rng (evil-select-block #'evil-up-xml-tag beg end type count nil t))) + (if (or (and beg (= beg (evil-range-beginning rng)) + end (= end (evil-range-end rng))) + (= (evil-range-beginning rng) (evil-range-end rng))) + (evil-select-block #'evil-up-xml-tag beg end type count t) + rng))) + (t + (evil-select-block #'evil-up-xml-tag beg end type count inclusive)))) + +(defun evil-expand-range (range &optional copy) + "Expand RANGE according to its type. +Return a new range if COPY is non-nil." + (when copy + (setq range (evil-copy-range range))) + (unless (plist-get (evil-range-properties range) :expanded) + (setq range (evil-transform-range :expand range))) + range) + +(defun evil-contract-range (range &optional copy) + "Contract RANGE according to its type. +Return a new range if COPY is non-nil." + (evil-transform-range :contract range copy)) + +(defun evil-normalize-range (range &optional copy) + "Normalize RANGE according to its type. +Return a new range if COPY is non-nil." + (evil-transform-range :normalize range copy)) + +(defun evil-transform-range (transform range &optional copy) + "Apply TRANSFORM to RANGE according to its type. +Return a new range if COPY is non-nil." + (when copy + (setq range (evil-copy-range range))) + (when (evil-type range) + (apply #'evil-set-range range + (apply #'evil-transform transform range))) + range) + +(defun evil-describe-range (range) + "Return description of RANGE. +If no description is available, return the empty string." + (apply #'evil-describe range)) + +;;; Undo + +(defun evil-start-undo-step (&optional continue) + "Start a undo step. +All following buffer modifications are grouped together as a +single action. If CONTINUE is non-nil, preceding modifications +are included. The step is terminated with `evil-end-undo-step'." + (when (and (listp buffer-undo-list) + (not evil-in-single-undo)) + (if evil-undo-list-pointer + (evil-refresh-undo-step) + (unless (or continue (null (car-safe buffer-undo-list))) + (undo-boundary)) + (setq evil-undo-list-pointer (or buffer-undo-list t))))) + +(defun evil-end-undo-step (&optional continue) + "End a undo step started with `evil-start-undo-step'. +Adds an undo boundary unless CONTINUE is specified." + (when (and (listp buffer-undo-list) + evil-undo-list-pointer + (not evil-in-single-undo)) + (evil-refresh-undo-step) + (unless (or continue (null (car-safe buffer-undo-list))) + (undo-boundary)) + (setq evil-undo-list-pointer nil))) + +(defun evil-refresh-undo-step () + "Refresh `buffer-undo-list' entries for current undo step. +Undo boundaries until `evil-undo-list-pointer' are removed to +make the entries undoable as a single action. See +`evil-start-undo-step'." + (when evil-undo-list-pointer + (setq buffer-undo-list + (evil-filter-list #'null buffer-undo-list evil-undo-list-pointer)) + (setq evil-undo-list-pointer (or buffer-undo-list t)))) + +(defmacro evil-with-undo (&rest body) + "Execute BODY with enabled undo. +If undo is disabled in the current buffer, the undo information +is stored in `evil-temporary-undo' instead of `buffer-undo-list'." + (declare (indent defun) + (debug t)) + `(unwind-protect + (let (buffer-undo-list) + (unwind-protect + (progn ,@body) + (setq evil-temporary-undo buffer-undo-list) + ;; ensure evil-temporary-undo starts with exactly one undo + ;; boundary marker, i.e. nil + (unless (null (car-safe evil-temporary-undo)) + (push nil evil-temporary-undo)))) + (unless (eq buffer-undo-list t) + ;; undo is enabled, so update the global buffer undo list + (setq buffer-undo-list + ;; prepend new undos (if there are any) + (if (cdr evil-temporary-undo) + (nconc evil-temporary-undo buffer-undo-list) + buffer-undo-list) + evil-temporary-undo nil)))) + +(defmacro evil-with-single-undo (&rest body) + "Execute BODY as a single undo step." + (declare (indent defun) + (debug t)) + `(let (evil-undo-list-pointer) + (evil-with-undo + (unwind-protect + (progn + (evil-start-undo-step) + (let ((evil-in-single-undo t)) + ,@body)) + (evil-end-undo-step))))) + +(defun evil-undo-pop () + "Undo the last buffer change. +Removes the last undo information from `buffer-undo-list'. +If undo is disabled in the current buffer, use the information +in `evil-temporary-undo' instead." + (let ((paste-undo (list nil))) + (let ((undo-list (if (eq buffer-undo-list t) + evil-temporary-undo + buffer-undo-list))) + (when (or (not undo-list) (car undo-list)) + (user-error "Can't undo previous change")) + (while (and undo-list (null (car undo-list))) + (pop undo-list)) ; remove nil + (while (and undo-list (car undo-list)) + (push (pop undo-list) paste-undo)) + (let ((buffer-undo-list (nreverse paste-undo))) + (evil-save-echo-area + (undo))) + (if (eq buffer-undo-list t) + (setq evil-temporary-undo nil) + (setq buffer-undo-list undo-list))))) + +;;; Search +(defun evil-transform-regexp (regexp replacements-alist) + (let ((pos 0) result) + (replace-regexp-in-string + "\\\\+[^\\\\]" + #'(lambda (txt) + (let* ((b (match-beginning 0)) + (e (match-end 0)) + (ch (aref txt (1- e))) + (repl (assoc ch replacements-alist))) + (if (and repl (zerop (mod (length txt) 2))) + (concat (substring txt b (- e 2)) + (cdr repl)) + txt))) + regexp nil t))) + +(defun evil-transform-magic (str magic quote transform &optional start) + "Transforms STR with magic characters. +MAGIC is a regexp that matches all potential magic +characters. Each occurence of CHAR as magic character within str +is replaced by the result of calling the associated TRANSFORM +function. TRANSFORM is a function taking two arguments, the +character to be transformed and the rest of string after the +character. The function should return a triple (REPLACEMENT REST +. STOP) where REPLACEMENT is the replacement and REST is the rest +of the string that has not been transformed. If STOP is non-nil +then the substitution stops immediately. The replacement starts +at position START, everything before that position is returned +literally. The result is a pair (RESULT . REST). RESULT is a +list containing the transformed parts in order. If two +subsequents parts are both strings, they are concatenated. REST +is the untransformed rest string (usually \"\" but may be more if +TRANSFORM stopped the substitution). Which characters are +considered as magic characters (i.e. the transformation happens +if the character is NOT preceeded by a backslash) is determined +by `evil-magic'. The special tokens \\v, \\V, \\m and \\M have +always a special meaning (like in Vim) and should not be +contained in TRANSFORMS, otherwise their meaning is overwritten. + +The parameter QUOTE is a quoting function applied to literal +transformations, usually `regexp-quote' or `replace-quote'." + (save-match-data + (let ((regexp (concat "\\(?:\\`\\|[^\\]\\)\\(\\\\\\(?:\\(" magic "\\)\\|\\(.\\)\\)\\|\\(" magic "\\)\\)")) + (magic-chars (evil-get-magic evil-magic)) + (evil-magic evil-magic) + (quote (or quote #'identity)) + result stop) + (while (and (not stop) str (string-match regexp str)) + (unless (zerop (match-beginning 1)) + (push (substring str 0 (match-beginning 1)) result)) + (let ((char (or (match-string 2 str) + (match-string 3 str) + (match-string 4 str))) + (rest (substring str (match-end 0)))) + (cond + ((match-beginning 4) + ;; magic character without backslash + (if (string-match magic-chars char) + ;; magic, do transform + (let ((trans (funcall transform (aref char 0) rest))) + (push (car trans) result) + (setq str (cadr trans) stop (nthcdr 2 trans))) + ;; non-magic, literal transformation + (push (funcall quote char) result) + (setq str rest))) + ((match-beginning 2) + ;; magic character with backslash + (if (not (string-match magic-chars char)) + ;; non-magic, do transform + (let ((trans (funcall transform (aref char 0) rest))) + (push (car trans) result) + (setq str (cadr trans) stop (nthcdr 2 trans))) + ;; magic, literal transformation + (push (funcall quote char) result) + (setq str rest))) + ((memq (aref char 0) '(?m ?M ?v ?V)) + (setq evil-magic (cdr (assq (aref char 0) + '((?m . t) + (?M . nil) + (?v . very-magic) + (?V . very-nomagic))))) + (setq magic-chars (evil-get-magic evil-magic)) + (setq str rest)) + (t + ;; non-magic char with backslash, literal transformation + (push (funcall quote char) result) + (setq str rest))))) + (cond + ((and str (not stop)) + (push str result) + (setq str "")) + ((not str) + (setq str ""))) + ;; concatenate subsequent strings + ;; note that result is in reverse order + (let (repl) + (while result + (cond + ((and (stringp (car result)) + (zerop (length (car result)))) + (pop result)) + ((and (stringp (car result)) + (stringp (cadr result))) + (setq result (cons (concat (cadr result) + (car result)) + (nthcdr 2 result)))) + (t + (push (pop result) repl)))) + (cons repl str))))) + +(defconst evil-vim-regexp-replacements + '((?n . "\n") (?r . "\r") + (?t . "\t") (?b . "\b") + (?s . "[[:space:]]") (?S . "[^[:space:]]") + (?d . "[[:digit:]]") (?D . "[^[:digit:]]") + (?x . "[[:xdigit:]]") (?X . "[^[:xdigit:]]") + (?o . "[0-7]") (?O . "[^0-7]") + (?a . "[[:alpha:]]") (?A . "[^[:alpha:]]") + (?l . "[a-z]") (?L . "[^a-z]") + (?u . "[A-Z]") (?U . "[^A-Z]") + (?y . "\\s") (?Y . "\\S") + (?\( . "\\(") (?\) . "\\)") + (?{ . "\\{") (?} . "\\}") + (?\[ . "[") (?\] . "]") + (?< . "\\<") (?> . "\\>") + (?_ . "\\_") + (?* . "*") (?+ . "+") + (?? . "?") (?= . "?") + (?. . ".") + (?` . "`") (?^ . "^") + (?$ . "$") (?| . "\\|"))) + +(defconst evil-regexp-magic "[][(){}<>_dDsSxXoOaAlLuUwWyY.*+?=^$`|nrtb]") + +(defun evil-transform-vim-style-regexp (regexp) + "Transforms vim-style backslash codes to Emacs regexp. +This includes the backslash codes \\d, \\D, \\s, \\S, \\x, \\X, +\\o, \\O, \\a, \\A, \\l, \\L, \\u, \\U and \\w, \\W. The new +codes \\y and \\Y can be used instead of the Emacs code \\s and +\\S which have a different meaning in Vim-style." + (car + (car + (evil-transform-magic + regexp evil-regexp-magic #'regexp-quote + #'(lambda (char rest) + (let ((repl (assoc char evil-vim-regexp-replacements))) + (if repl + (list (cdr repl) rest) + (list (concat "\\" (char-to-string char)) rest)))))))) + +;;; Substitute + +(defun evil-downcase-first (str) + "Return STR with the first letter downcased." + (if (zerop (length str)) + str + (concat (downcase (substring str 0 1)) + (substring str 1)))) + +(defun evil-upcase-first (str) + "Return STR with the first letter upcased." + (if (zerop (length str)) + str + (concat (upcase (substring str 0 1)) + (substring str 1)))) + +(defun evil-get-magic (magic) + "Returns a regexp matching the magic characters according to MAGIC. +Depending on the value of MAGIC the following characters are +considered magic. + t [][{}*+?.&~$^ + nil [][{}*+?$^ + 'very-magic not 0-9A-Za-z_ + 'very-nomagic empty." + (cond + ((eq magic t) "[][}{*+?.&~$^]") + ((eq magic 'very-magic) "[^0-9A-Za-z_]") + ((eq magic 'very-nomagic) "\\\\") + (t "[][}{*+?$^]"))) + +;; TODO: support magic characters in patterns +(defconst evil-replacement-magic "[eElLuU0-9&#,rnbt=]" + "All magic characters in a replacement string") + +(defun evil-compile-subreplacement (to &optional start) + "Convert a regexp replacement TO to Lisp from START until \\e or \\E. +Returns a pair (RESULT . REST). RESULT is a list suitable for +`perform-replace' if necessary, the original string if not. +REST is the unparsed remainder of TO." + (let ((result + (evil-transform-magic + to evil-replacement-magic #'replace-quote + #'(lambda (char rest) + (cond + ((eq char ?#) + (list '(number-to-string replace-count) rest)) + ((eq char ?r) (list "\r" rest)) + ((eq char ?n) (list "\n" rest)) + ((eq char ?b) (list "\b" rest)) + ((eq char ?t) (list "\t" rest)) + ((memq char '(?e ?E)) + `("" ,rest . t)) + ((memq char '(?l ?L ?u ?U)) + (let ((result (evil-compile-subreplacement rest)) + (func (cdr (assoc char + '((?l . evil-downcase-first) + (?L . downcase) + (?u . evil-upcase-first) + (?U . upcase)))))) + (list `(,func + (replace-quote + (evil-match-substitute-replacement + ,(car result) + (not case-replace)))) + (cdr result)))) + ((eq char ?=) + (when (or (zerop (length rest)) + (not (eq (aref rest 0) ?@))) + (user-error "Expected @ after \\=")) + (when (< (length rest) 2) + (user-error "Expected register after \\=@")) + (list (evil-get-register (aref rest 1)) + (substring rest 2))) + ((eq char ?,) + (let* ((obj (read-from-string rest)) + (result `(replace-quote ,(car obj))) + (end + ;; swallow a space after a symbol + (if (and (or (symbolp (car obj)) + ;; swallow a space after 'foo, + ;; but not after (quote foo) + (and (eq (car-safe (car obj)) 'quote) + (not (= ?\( (aref rest 0))))) + (eq (string-match " " rest (cdr obj)) + (cdr obj))) + (1+ (cdr obj)) + (cdr obj)))) + (list result (substring rest end)))) + ((eq char ?0) + (list "\\&" rest)) + (t + (list (concat "\\" (char-to-string char)) rest)))) + start))) + (let ((rest (cdr result)) + (result (car result))) + (replace-match-string-symbols result) + (cons (if (cdr result) + (cons 'concat result) + (or (car result) "")) + rest)))) + +(defun evil-compile-replacement (to) + "Maybe convert a regexp replacement TO to Lisp. +Returns a list suitable for `perform-replace' if necessary, the +original string if not. Currently the following magic characters +in replacements are supported: 0-9&#lLuUrnbt, +The magic character , (comma) start an Emacs-lisp expression." + (when (stringp to) + (save-match-data + (cons 'replace-eval-replacement + (car (evil-compile-subreplacement to)))))) + +(defun evil-replace-match (replacement &optional fixedcase string) + "Replace text match by last search with REPLACEMENT. +If REPLACEMENT is an expression it will be evaluated to compute +the replacement text, otherwise the function behaves as +`replace-match'." + (if (stringp replacement) + (replace-match replacement fixedcase nil string) + (replace-match (funcall (car replacement) + (cdr replacement) + 0) + fixedcase nil string))) + +(defun evil-match-substitute-replacement (replacement &optional fixedcase string) + "Return REPLACEMENT as it will be inserted by `evil-replace-match'." + (if (stringp replacement) + (match-substitute-replacement replacement fixedcase nil string) + (match-substitute-replacement (funcall (car replacement) + (cdr replacement) + 0) + fixedcase nil string))) + +;;; Alignment + +(defun evil-justify-lines (beg end justify position) + "Justifes all lines in a range. +BEG and END specify the range of those lines to be +justified. JUSTIFY is either 'left, 'right or 'center according +to the justification type. POSITION is the maximal text width for +right and center justification or the column at which the lines +should be left-aligned for left justification." + (let ((fill-column position) + adaptive-fill-mode fill-prefix) + (evil-with-restriction + (save-excursion + (goto-char beg) + (line-beginning-position)) + (save-excursion + (goto-char end) + (if (bolp) + (line-end-position 0) + (line-end-position))) + (goto-char (point-min)) + (while (progn + (if (eq justify 'left) + (indent-line-to position) + (when (re-search-forward "^[[:space:]]*" nil t) + (delete-region (match-beginning 0) + (match-end 0))) + (justify-current-line justify nil t)) + (and (zerop (forward-line)) (bolp)))) + (goto-char (point-min)) + (back-to-indentation)))) + +;;; View helper + +(defvar-local evil-list-view-select-action nil) +(put 'evil-list-view-select-action 'permanent-local t) + +(define-derived-mode evil-list-view-mode tabulated-list-mode + "Evil List View" + (tabulated-list-init-header) + (tabulated-list-print)) + +(defun evil-list-view-goto-entry () + (interactive) + (when (and evil-list-view-select-action + (not (eobp))) + (let* ((line (line-number-at-pos (point))) + (entry (elt tabulated-list-entries (1- line)))) + (funcall evil-list-view-select-action (nth 1 entry))))) + +(define-key evil-list-view-mode-map (kbd "q") #'kill-this-buffer) +(define-key evil-list-view-mode-map [follow-link] nil) ;; allows mouse-1 to be activated +(define-key evil-list-view-mode-map [mouse-1] #'evil-list-view-goto-entry) +(define-key evil-list-view-mode-map [return] #'evil-list-view-goto-entry) + +(defmacro evil-with-view-list (&rest properties) + "Opens new list view buffer. + +PROPERTIES is a property-list which supports the following properties: + +:name (required) The name of the buffer. +:mode-name (required) The name for the mode line. +:format (required) The value for `tabulated-list-format'. +:entries (required) The value for `tabulated-list-entries'. +:select-action (optional) A function for row selection. + It takes in a single parameter, which is the selected row's + vector value that is passed into `:entries'. +" + (declare (indent defun) (debug t)) + `(let ((bufname (concat "*" ,(plist-get properties :name) "*")) + (inhibit-read-only t)) + (and (get-buffer bufname) + (kill-buffer bufname)) + (let ((buf (get-buffer-create bufname))) + (with-current-buffer buf + (setq tabulated-list-format ,(plist-get properties :format)) + (setq tabulated-list-entries ,(plist-get properties :entries)) + (setq evil-list-view-select-action ,(plist-get properties :select-action)) + (evil-list-view-mode) + (setq mode-name ,(plist-get properties :mode-name)) + (evil-motion-state)) + (switch-to-buffer-other-window buf)))) + +(provide 'evil-common) + +;;; evil-common.el ends here |