;;; evil-macros.el --- Macros
;; 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-common)
(require 'evil-states)
(require 'evil-repeat)
;;; Code:
(declare-function evil-ex-p "evil-ex")
;; set some error codes
(put 'beginning-of-line 'error-conditions '(beginning-of-line error))
(put 'beginning-of-line 'error-message "Beginning of line")
(put 'end-of-line 'error-conditions '(end-of-line error))
(put 'end-of-line 'error-message "End of line")
(defun evil-motion-range (motion &optional count type)
"Execute a motion and return the buffer positions.
The return value is a list (BEG END TYPE)."
(let ((opoint (point))
(omark (mark t))
(omactive (and (boundp 'mark-active) mark-active))
(obuffer (current-buffer))
(evil-motion-marker (move-marker (make-marker) (point)))
range)
(evil-with-transient-mark-mode
(evil-narrow-to-field
(unwind-protect
(let ((current-prefix-arg count)
;; Store type in global variable `evil-this-type'.
;; If necessary, motions can change their type
;; during execution by setting this variable.
(evil-this-type
(or type (evil-type motion 'exclusive))))
(condition-case err
(let ((repeat-type (evil-repeat-type motion t)))
(if (functionp repeat-type)
(funcall repeat-type 'pre))
(unless (with-local-quit
(setq range (call-interactively motion))
t)
(evil-repeat-abort)
(setq quit-flag t))
(if (functionp repeat-type)
(funcall repeat-type 'post)))
(error (prog1 nil
(evil-repeat-abort)
;; some operators depend on succeeding
;; motions, in particular for
;; `evil-forward-char' (e.g., used by
;; `evil-substitute'), therefore we let
;; end-of-line and end-of-buffer pass
(if (not (memq (car err) '(end-of-line end-of-buffer)))
(signal (car err) (cdr err))
(message (error-message-string err))))))
(cond
;; the motion returned a range
((evil-range-p range))
;; the motion made a Visual selection
((evil-visual-state-p)
(setq range (evil-visual-range)))
;; the motion made an active region
((region-active-p)
(setq range (evil-range (region-beginning)
(region-end)
evil-this-type)))
;; default: range from previous position to current
(t
(setq range (evil-expand-range
(evil-normalize evil-motion-marker
(point)
evil-this-type)))))
(unless (or (null type) (eq (evil-type range) type))
(evil-set-type range type)
(evil-expand-range range))
(evil-set-range-properties range nil)
range)
;; restore point and mark like `save-excursion',
;; but only if the motion hasn't disabled the operator
(unless evil-inhibit-operator
(set-buffer obuffer)
(evil-move-mark omark)
(goto-char opoint))
;; delete marker so it doesn't slow down editing
(move-marker evil-motion-marker nil))))))
(defmacro evil-define-motion (motion args &rest body)
"Define an motion command MOTION.
\(fn MOTION (COUNT ARGS...) DOC [[KEY VALUE]...] BODY...)"
(declare (indent defun)
(debug (&define name lambda-list
[&optional stringp]
[&rest keywordp sexp]
[&optional ("interactive" [&rest form])]
def-body)))
(let (arg doc interactive key keys type)
(when args
(setq args `(&optional ,@(delq '&optional args))
;; the count is either numerical or nil
interactive '("<c>")))
;; collect docstring
(when (and (> (length body) 1)
(or (eq (car-safe (car-safe body)) 'format)
(stringp (car-safe body))))
(setq doc (pop body)))
;; collect keywords
(setq keys (plist-put keys :repeat 'motion))
(while (keywordp (car-safe body))
(setq key (pop body)
arg (pop body)
keys (plist-put keys key arg)))
;; collect `interactive' specification
(when (eq (car-safe (car-safe body)) 'interactive)
(setq interactive (cdr (pop body))))
;; macro expansion
`(progn
;; refresh echo area in Eldoc mode
(when ',motion
(eval-after-load 'eldoc
'(and (fboundp 'eldoc-add-command)
(eldoc-add-command ',motion))))
(evil-define-command ,motion (,@args)
,@(when doc `(,doc)) ; avoid nil before `interactive'
,@keys
:keep-visual t
(interactive ,@interactive)
,@body))))
(defmacro evil-narrow-to-line (&rest body)
"Narrow BODY to the current line.
BODY will signal the errors 'beginning-of-line or 'end-of-line
upon reaching the beginning or end of the current line.
\(fn [[KEY VAL]...] BODY...)"
(declare (indent defun)
(debug t))
`(let* ((range (evil-expand (point) (point) 'line))
(beg (evil-range-beginning range))
(end (evil-range-end range))
(min (point-min))
(max (point-max)))
(when (save-excursion (goto-char end) (bolp))
(setq end (max beg (1- end))))
;; don't include the newline in Normal state
(when (and evil-move-cursor-back
(not evil-move-beyond-eol)
(not (evil-visual-state-p))
(not (evil-operator-state-p)))
(setq end (max beg (1- end))))
(evil-with-restriction beg end
(evil-signal-without-movement
(condition-case err
(progn ,@body)
(beginning-of-buffer
(if (= beg min)
(signal (car err) (cdr err))
(signal 'beginning-of-line nil)))
(end-of-buffer
(if (= end max)
(signal (car err) (cdr err))
(signal 'end-of-line nil))))))))
;; we don't want line boundaries to trigger the debugger
;; when `debug-on-error' is t
(add-to-list 'debug-ignored-errors "^Beginning of line$")
(add-to-list 'debug-ignored-errors "^End of line$")
(defun evil-eobp (&optional pos)
"Whether point is at end-of-buffer with regard to end-of-line."
(save-excursion
(when pos (goto-char pos))
(cond
((eobp))
;; the rest only pertains to Normal state
((not (evil-normal-state-p))
nil)
;; at the end of the last line
((eolp)
(forward-char)
(eobp))
;; at the last character of the last line
(t
(forward-char)
(cond
((eobp))
((eolp)
(forward-char)
(eobp)))))))
(defun evil-move-beginning (count forward &optional backward)
"Move to the beginning of the COUNT next object.
If COUNT is negative, move to the COUNT previous object.
FORWARD is a function which moves to the end of the object, and
BACKWARD is a function which moves to the beginning.
If one is unspecified, the other is used with a negative argument."
(let* ((count (or count 1))
(backward (or backward
#'(lambda (count)
(funcall forward (- count)))))
(forward (or forward
#'(lambda (count)
(funcall backward (- count)))))
(opoint (point)))
(cond
((< count 0)
(when (bobp)
(signal 'beginning-of-buffer nil))
(unwind-protect
(evil-motion-loop (nil count count)
(funcall backward 1))
(unless (zerop count)
(goto-char (point-min)))))
((> count 0)
(when (evil-eobp)
(signal 'end-of-buffer nil))
;; Do we need to move past the current object?
(when (<= (save-excursion
(funcall forward 1)
(funcall backward 1)
(point))
opoint)
(setq count (1+ count)))
(unwind-protect
(evil-motion-loop (nil count count)
(funcall forward 1))
(if (zerop count)
;; go back to beginning of object
(funcall backward 1)
(goto-char (point-max)))))
(t
count))))
(defun evil-move-end (count forward &optional backward inclusive)
"Move to the end of the COUNT next object.
If COUNT is negative, move to the COUNT previous object.
FORWARD is a function which moves to the end of the object, and
BACKWARD is a function which moves to the beginning.
If one is unspecified, the other is used with a negative argument.
If INCLUSIVE is non-nil, then point is placed at the last character
of the object; otherwise it is placed at the end of the object."
(let* ((count (or count 1))
(backward (or backward
#'(lambda (count)
(funcall forward (- count)))))
(forward (or forward
#'(lambda (count)
(funcall backward (- count)))))
(opoint (point)))
(cond
((< count 0)
(when (bobp)
(signal 'beginning-of-buffer nil))
;; Do we need to move past the current object?
(when (>= (save-excursion
(funcall backward 1)
(funcall forward 1)
(point))
(if inclusive
(1+ opoint)
opoint))
(setq count (1- count)))
(unwind-protect
(evil-motion-loop (nil count count)
(funcall backward 1))
(if (not (zerop count))
(goto-char (point-min))
;; go to end of object
(funcall forward 1)
(when inclusive
(unless (bobp) (backward-char)))
(when (or (evil-normal-state-p)
(evil-motion-state-p))
(evil-adjust-cursor t)))))
((> count 0)
(when (evil-eobp)
(signal 'end-of-buffer nil))
(when inclusive
(forward-char))
(unwind-protect
(evil-motion-loop (nil count count)
(funcall forward 1))
(if (not (zerop count))
(goto-char (point-max))
(when inclusive
(unless (bobp) (backward-char)))
(when (or (evil-normal-state-p)
(evil-motion-state-p))
(evil-adjust-cursor t)))))
(t
count))))
(defun evil-text-object-make-linewise (range)
"Turn the text object selection RANGE to linewise.
The selection is adjusted in a sensible way so that the selected
lines match the user intent. In particular, whitespace-only parts
at the first and last lines are omitted. This function returns
the new range."
;; Bug #607
;; If new type is linewise and the selection of the
;; first line consists of whitespace only, the
;; beginning is moved to the start of the next line. If
;; the selections of the last line consists of
;; whitespace only, the end is moved to the end of the
;; previous line.
(if (eq (evil-type range) 'line)
range
(let ((expanded (plist-get (evil-range-properties range) :expanded))
(newrange (evil-expand-range range t)))
(save-excursion
;; skip whitespace at the beginning
(goto-char (evil-range-beginning newrange))
(skip-chars-forward " \t")
(when (and (not (bolp)) (eolp))
(evil-set-range-beginning newrange (1+ (point))))
;; skip whitepsace at the end
(goto-char (evil-range-end newrange))
(skip-chars-backward " \t")
(when (and (not (eolp)) (bolp))
(evil-set-range-end newrange (1- (point))))
;; only modify range if result is not empty
(if (> (evil-range-beginning newrange)
(evil-range-end newrange))
range
(unless expanded
(evil-contract-range newrange))
newrange)))))
(defmacro evil-define-text-object (object args &rest body)
"Define a text object command OBJECT.
BODY should return a range (BEG END) to the right of point
if COUNT is positive, and to the left of it if negative.
\(fn OBJECT (COUNT) DOC [[KEY VALUE]...] BODY...)"
(declare (indent defun)
(debug (&define name lambda-list
[&optional stringp]
[&rest keywordp sexp]
def-body)))
(let* ((args (delq '&optional args))
(count (or (pop args) 'count))
(args (when args `(&optional ,@args)))
(interactive '((interactive "<c><v>")))
arg doc key keys)
;; collect docstring
(when (stringp (car-safe body))
(setq doc (pop body)))
;; collect keywords
(setq keys (plist-put keys :extend-selection t))
(while (keywordp (car-safe body))
(setq key (pop body)
arg (pop body)
keys (plist-put keys key arg)))
;; interactive
(when (eq (car-safe (car-safe body)) 'interactive)
(setq interactive (list (pop body))))
;; macro expansion
`(evil-define-motion ,object (,count ,@args)
,@(when doc `(,doc))
,@keys
,@interactive
(setq ,count (or ,count 1))
(when (/= ,count 0)
(let ((type (evil-type ',object evil-visual-char))
(extend (and (evil-visual-state-p)
(evil-get-command-property
',object :extend-selection
',(plist-get keys :extend-selection))))
(dir evil-visual-direction)
mark point range selection)
(cond
;; Visual state: extend the current selection
((and (evil-visual-state-p)
(called-interactively-p 'any))
;; if we are at the beginning of the Visual selection,
;; go to the left (negative COUNT); if at the end,
;; go to the right (positive COUNT)
(setq dir evil-visual-direction
,count (* ,count dir))
(setq range (progn ,@body))
(when (evil-range-p range)
(setq range (evil-expand-range range))
(evil-set-type range (evil-type range type))
(setq range (evil-contract-range range))
;; the beginning is mark and the end is point
;; unless the selection goes the other way
(setq mark (evil-range-beginning range)
point (evil-range-end range)
type (evil-type
(if evil-text-object-change-visual-type
range
(evil-visual-range))))
(when (and (eq type 'line)
(not (eq type (evil-type range))))
(let ((newrange (evil-text-object-make-linewise range)))
(setq mark (evil-range-beginning newrange)
point (evil-range-end newrange))))
(when (< dir 0)
(evil-swap mark point))
;; select the union
(evil-visual-make-selection mark point type)))
;; not Visual state: return a pair of buffer positions
(t
(setq range (progn ,@body))
(unless (evil-range-p range)
(setq ,count (- ,count)
range (progn ,@body)))
(when (evil-range-p range)
(setq selection (evil-range (point) (point) type))
(if extend
(setq range (evil-range-union range selection))
(evil-set-type range (evil-type range type)))
;; possibly convert to linewise
(when (eq evil-this-type-modified 'line)
(setq range (evil-text-object-make-linewise range)))
(evil-set-range-properties range nil)
range))))))))
(defmacro evil-define-operator (operator args &rest body)
"Define an operator command OPERATOR.
\(fn OPERATOR (BEG END ARGS...) DOC [[KEY VALUE]...] BODY...)"
(declare (indent defun)
(debug (&define name lambda-list
[&optional stringp]
[&rest keywordp sexp]
[&optional ("interactive" [&rest form])]
def-body)))
(let* ((args (delq '&optional args))
(interactive (if (> (length args) 2) '("<R>") '("<r>")))
(args (if (> (length args) 2)
`(,(nth 0 args) ,(nth 1 args)
&optional ,@(nthcdr 2 args))
args))
arg doc key keys visual)
;; collect docstring
(when (and (> (length body) 1)
(or (eq (car-safe (car-safe body)) 'format)
(stringp (car-safe body))))
(setq doc (pop body)))
;; collect keywords
(setq keys (plist-put keys :move-point t))
(while (keywordp (car-safe body))
(setq key (pop body)
arg (pop body))
(cond
((eq key :keep-visual)
(setq visual arg))
(t
(setq keys (plist-put keys key arg)))))
;; collect `interactive' specification
(when (eq (car-safe (car-safe body)) 'interactive)
(setq interactive (cdr-safe (pop body))))
;; transform extended interactive specs
(setq interactive (apply #'evil-interactive-form interactive))
(setq keys (evil-concat-plists keys (cdr-safe interactive))
interactive (car-safe interactive))
;; macro expansion
`(evil-define-command ,operator ,args
,@(when doc `(,doc))
,@keys
:keep-visual t
:suppress-operator t
(interactive
(let* ((evil-operator-range-motion
(when (evil-has-command-property-p ',operator :motion)
;; :motion nil is equivalent to :motion undefined
(or (evil-get-command-property ',operator :motion)
#'undefined)))
(evil-operator-range-type
(evil-get-command-property ',operator :type))
(orig (point))
evil-operator-range-beginning
evil-operator-range-end
evil-inhibit-operator)
(setq evil-inhibit-operator-value nil
evil-this-operator this-command)
(prog1 ,interactive
(setq orig (point)
evil-inhibit-operator-value evil-inhibit-operator)
(if ,visual
(when (evil-visual-state-p)
(evil-visual-expand-region))
(when (or (evil-visual-state-p) (region-active-p))
(setq deactivate-mark t)))
(cond
((evil-visual-state-p)
(evil-visual-rotate 'upper-left))
((evil-get-command-property ',operator :move-point)
(goto-char (or evil-operator-range-beginning orig)))
(t
(goto-char orig))))))
(unwind-protect
(let ((evil-inhibit-operator evil-inhibit-operator-value))
(unless (and evil-inhibit-operator
(called-interactively-p 'any))
,@body))
(setq evil-inhibit-operator-value nil)))))
;; this is used in the `interactive' specification of an operator command
(defun evil-operator-range (&optional return-type)
"Read a motion from the keyboard and return its buffer positions.
The return value is a list (BEG END), or (BEG END TYPE) if
RETURN-TYPE is non-nil."
(let* ((evil-ex-p (and (not (minibufferp)) (evil-ex-p)))
(motion (or evil-operator-range-motion
(when evil-ex-p 'evil-line)))
(type evil-operator-range-type)
(range (evil-range (point) (point)))
command count modifier)
(setq evil-this-type-modified nil)
(evil-save-echo-area
(cond
;; Ex mode
((and evil-ex-p evil-ex-range)
(setq range evil-ex-range))
;; Visual selection
((and (not evil-ex-p) (evil-visual-state-p))
(setq range (evil-visual-range)))
;; active region
((and (not evil-ex-p) (region-active-p))
(setq range (evil-range (region-beginning)
(region-end)
(or evil-this-type 'exclusive))))
(t
;; motion
(evil-save-state
(unless motion
(evil-change-state 'operator)
;; Make linewise operator shortcuts. E.g., "d" yields the
;; shortcut "dd", and "g?" yields shortcuts "g??" and "g?g?".
(let ((keys (nth 2 (evil-extract-count (this-command-keys)))))
(setq keys (listify-key-sequence keys))
(dotimes (var (length keys))
(define-key evil-operator-shortcut-map
(vconcat (nthcdr var keys)) 'evil-line)))
;; read motion from keyboard
(setq command (evil-read-motion motion)
motion (nth 0 command)
count (nth 1 command)
type (or type (nth 2 command))))
(cond
((eq motion #'undefined)
(setq range (if return-type '(nil nil nil) '(nil nil))
motion nil))
((or (null motion) ; keyboard-quit
(evil-get-command-property motion :suppress-operator))
(when (fboundp 'evil-repeat-abort)
(evil-repeat-abort))
(setq quit-flag t
motion nil))
(evil-repeat-count
(setq count evil-repeat-count
;; only the first operator's count is overwritten
evil-repeat-count nil))
((or count current-prefix-arg)
;; multiply operator count and motion count together
(setq count
(* (prefix-numeric-value count)
(prefix-numeric-value current-prefix-arg)))))
(when motion
(let ((evil-state 'operator)
mark-active)
;; calculate motion range
(setq range (evil-motion-range
motion
count
type))))
;; update global variables
(setq evil-this-motion motion
evil-this-motion-count count
type (evil-type range type)
evil-this-type type))))
(when (evil-range-p range)
(unless (or (null type) (eq (evil-type range) type))
(evil-contract-range range)
(evil-set-type range type)
(evil-expand-range range))
(evil-set-range-properties range nil)
(unless return-type
(evil-set-type range nil))
(setq evil-operator-range-beginning (evil-range-beginning range)
evil-operator-range-end (evil-range-end range)
evil-operator-range-type (evil-type range)))
range)))
(defmacro evil-define-type (type doc &rest body)
"Define type TYPE.
DOC is a general description and shows up in all docstrings.
It is followed by a list of keywords and functions:
:expand FUNC Expansion function. This function should accept
two positions in the current buffer, BEG and END,
and return a pair of expanded buffer positions.
:contract FUNC The opposite of :expand, optional.
:one-to-one BOOL Whether expansion is one-to-one. This means that
:expand followed by :contract always returns the
original range.
:normalize FUNC Normalization function, optional. This function should
accept two unexpanded positions and adjust them before
expansion. May be used to deal with buffer boundaries.
:string FUNC Description function. This takes two buffer positions
and returns a human-readable string, for example,
\"2 lines\".
If further keywords and functions are specified, they are assumed to
be transformations on buffer positions, like :expand and :contract.
\(fn TYPE DOC [[KEY FUNC]...])"
(declare (indent defun)
(debug (&define name
[&optional stringp]
[&rest [keywordp function-form]])))
(let (args defun-forms func key name plist string sym val)
;; standard values
(setq plist (plist-put plist :one-to-one t))
;; keywords
(while (keywordp (car-safe body))
(setq key (pop body)
val (pop body))
(if (plist-member plist key) ; not a function
(setq plist (plist-put plist key val))
(setq func val
sym (intern (replace-regexp-in-string
"^:" "" (symbol-name key)))
name (intern (format "evil-%s-%s" type sym))
args (car (cdr-safe func))
string (car (cdr (cdr-safe func)))
string (if (stringp string)
(format "%s\n\n" string) "")
plist (plist-put plist key `',name))
(add-to-list
'defun-forms
(cond
((eq key :string)
`(defun ,name (beg end &rest properties)
,(format "Return size of %s from BEG to END \
with PROPERTIES.\n\n%s%s" type string doc)
(let ((beg (evil-normalize-position beg))
(end (evil-normalize-position end))
(type ',type)
plist range)
(when (and beg end)
(save-excursion
(evil-sort beg end)
(unless (plist-get properties :expanded)
(setq range (apply #'evil-expand
beg end type properties)
beg (evil-range-beginning range)
end (evil-range-end range)
type (evil-type range type)
plist (evil-range-properties range))
(setq properties
(evil-concat-plists properties plist)))
(or (apply #',func beg end
(when ,(> (length args) 2)
properties))
""))))))
(t
`(defun ,name (beg end &rest properties)
,(format "Perform %s transformation on %s from BEG to END \
with PROPERTIES.\n\n%s%s" sym type string doc)
(let ((beg (evil-normalize-position beg))
(end (evil-normalize-position end))
(type ',type)
plist range)
(when (and beg end)
(save-excursion
(evil-sort beg end)
(when (memq ,key '(:expand :contract))
(setq properties
(plist-put properties
:expanded
,(eq key :expand))))
(setq range (or (apply #',func beg end
(when ,(> (length args) 2)
properties))
(apply #'evil-range
beg end type properties))
beg (evil-range-beginning range)
end (evil-range-end range)
type (evil-type range type)
plist (evil-range-properties range))
(setq properties
(evil-concat-plists properties plist))
(apply #'evil-range beg end type properties)))))))
t)))
;; :one-to-one requires both or neither of :expand and :contract
(when (plist-get plist :expand)
(setq plist (plist-put plist :one-to-one
(and (plist-get plist :contract)
(plist-get plist :one-to-one)))))
`(progn
(evil-put-property 'evil-type-properties ',type ,@plist)
,@defun-forms
',type)))
(defmacro evil-define-interactive-code (code &rest body)
"Define an interactive code.
PROMPT, if given, is the remainder of the interactive string
up to the next newline. Command properties may be specified
via KEY-VALUE pairs. BODY should evaluate to a list of values.
\(fn CODE (PROMPT) [[KEY VALUE]...] BODY...)"
(declare (indent defun))
(let* ((args (when (and (> (length body) 1)
(listp (car-safe body)))
(pop body)))
(doc (when (stringp (car-safe body)) (pop body)))
func properties)
(while (keywordp (car-safe body))
(setq properties
(append properties (list (pop body) (pop body)))))
(cond
(args
(setq func `(lambda ,args
,@(when doc `(,doc))
,@body)))
((> (length body) 1)
(setq func `(progn ,@body)))
(t
(setq func (car body))))
`(eval-and-compile
(let* ((code ,code)
(entry (assoc code evil-interactive-alist))
(value (cons ',func ',properties)))
(if entry
(setcdr entry value)
(push (cons code value) evil-interactive-alist))
code))))
;;; Highlighting
(when (fboundp 'font-lock-add-keywords)
(font-lock-add-keywords
'emacs-lisp-mode
;; Match all `evil-define-' forms except `evil-define-key'.
;; (In the interests of speed, this expression is incomplete
;; and does not match all three-letter words.)
'(("(\\(evil-\\(?:ex-\\)?define-\
\\(?:[^ k][^ e][^ y]\\|[-[:word:]]\\{4,\\}\\)\\)\
\\>[ \f\t\n\r\v]*\\(\\(?:\\sw\\|\\s_\\)+\\)?"
(1 font-lock-keyword-face)
(2 font-lock-function-name-face nil t))
("(\\(evil-\\(?:delay\\|narrow\\|signal\\|save\\|with\\(?:out\\)?\\)\
\\(?:-[-[:word:]]+\\)?\\)\\>\[ \f\t\n\r\v]+"
1 font-lock-keyword-face)
("(\\(evil-\\(?:[-[:word:]]\\)*loop\\)\\>[ \f\t\n\r\v]+"
1 font-lock-keyword-face))))
(provide 'evil-macros)
;;; evil-macros.el ends here