This package is like a combination of the targets, TextObjectify, anyblock, and expand-region vim plugins.
Please note that this package is in an early stage of development. This package is not yet polished, and much of the planned functionality is not yet implemented.
- Pairs can be regexps
- No N or L (see rationale below)
- Adds “remote” text objects (selected with avy)
- Objects are supported (e.g.
danw
; this is a todo item for targets.vim) - Currently no support for tag text objects and no argument text object
- Not polished (please make issues)
Targets.vim specifies three kinds of text objects. This package adds another three to that list:
- pair (created using
evil-select-paren
) - quote (created using
evil-select-quote
) - separator (created using
evil-select-paren
) - object (created with
evil-select-*-object
; uses thing-at-point) - composite (a combination of any number of the above)
- simple (e.g. created using
evil-range
; extran
,l
, etc. text objects not supported; not yet implemented)
A pair consists of an opening and closing delimiter that are different from each other. Each can be a single character or a regexp.
A quote consists of an opening and closing delimiter that are the same. The delimiter must be a single character. Unlike with evil’s default quote text objects, a
will not select any surrounding whitespace (this functionality is moved to A
). There is an option to keep the default behavior (not yet implemented).
The main difference between a separator and a quote is that there do not need to be a balanced number of separators. A separator delimiter can be a regexp. a
also behaves slightly differently. It includes the first separator but not the second, so that after a deletion, a separator will still be left in between the text on either side.
These are text objects that use thingatpt.el
such as evil-word
and evil-sentence
. Note that i
and a
will act as I
and A
for most text objects by default (e.g. there are no delimiters to consider for an evil-word
). I
and A
text objects will still be created because it is still possible and sometimes useful to have different behavior for inner vs. outer things besides how they handle whitespace (e.g. you could potentially create an inner sentence text object that excludes quotations and org markup like sentence-navigation.el). You can specify a shrink function to use for the inner text object to achieve this behavior.
Since this package is about adding extra text objects, it also adds a few other specific text objects that evil does not have by default:
- line (not yet implemented)
- buffer (not yet implemented)
- argument (as a regexp pair; not yet implemented)
In addition to the standard inner and outer text objects, the targets.vim
plugin adds [I]nside
and [A]round
. In this package, these act how i
and a
do for objects (such as evil-word
) except they are for the pair, quote, and separator types. I
will select inside the delimiters excluding any whitespace, and A
will select all the text encompassed by the delimiters, the delimiters, and either trailing or leading whitespace. Trailing whitespace is preferred.
[n]ext
and [l]ast
text objects are also added. These will select the next and previous text object and can take a count. For example, vin(
would select the next parens, and v2in(
would select the parens after those. When used with a non-selection operator (such as delete), the point will not be moved.
The targets.vim
plugin also has N
and L
(which act as n
and l
with a doubled count). These do not exist for quotes since seeking behavior is intelligent in this package (a proper quote will always be selected). They do not exist for separators because it is easy to double your count if you wish to.
Finally, there is a [r]emote
text object. This allows you to select the text object to act on using avy. avy-resume
is supported but will only move the point to the corresponding text object. evil-repeat
can be used to repeat the action as well. For example, with evil-exchange, g x a s <avy-keys> . <avy-keys>
could be used to swap two sentences remotely.
Note that the keys used for these can be customized.
Unlike in vim, evil already implements some of the features added in the aforementioned vim plugins. Evil’s quote text objects can cover multiple lines, and paren text objects will seek forward if defined with strings instead of characters (like in TextObjectify
). Evil’s quote text objects are smart and will only select within a proper quote (and not the space in between quoted text; like in targets
). Evil’s text objects also support counts and expanding a selection when called again (like targets
and expand-region
). Adding composite text objects (like anyblock
has) allows for expanding a region to fill the next of any number of text objects.
By default, i(
, for example, will not seek forward. evil-inner-paren
can be redefined to seek forward by using strings instead of characters for the open and close paren, but the behavior will not always be as intelligent. This is because evil-up-block
(evil-up-paren
is used for the default paren text objects) does not consider whether delimiters are escaped or whether they are in the same string or comment. Evil also does not have backward seeking.
Instead of attempting to add more types of seeking to every evil selection function directly, this package just ignores the default seeking. If there is no text object at the point, this package will try the functions in targets-seek-functions
in order in an attempt to find a text object. Like in targets.vim
, seeking is customizable (by changing the functions in targets-seek-functions
). By default, every text object defined with this package will seek first forward and then backward.
If you want to create a new seeking function, you can look at the functions in targets-seek-functions
and at the arguments they take. It is more likely that you will only want to make slight changes to the seeking behavior. For example, if you don’t want backward seeking, you can remove that function from the list. If you don’t want any seeking, you can set the list to nil
. By default, seeking is bounded to the text visible in the window. To alter the bounds, the targets-bound
variable can changed to specify a user-created function. See the default function’s docstring for information on how a user-created function should behave.
Like in targets.vim
, you can also customize when seeking causes a new entry to be added to the jump list. To do this, you can change the targets-push-jump-p
variable to specify a different function. See the default function’s docstring for more information. By default, a new entry is added when seeking moves the point to a different line.
This package provides three main macros for creating text objects.
This is the main way provided by this package for creating text objects in bulk.
Here is a basic example with all the required arguments:
(targets-define-to paren "(" ")" pair)
This will result in the creation of 16 text objects (inner
, a
, inside
, around
, and the next, last, and remote versions of these).
The first argument is a symbol that will be used when naming the new text objects. The next two arguments specify the delimiters for the text objects. The fourth arguments specifies the type of the text objects. The names for the types are pair
, quote
, separator
, and object
. Only pairs require the closing delimiter. The opening delimiter should always be a string except for an object
, for which it should be the symbol corresponding to the thing.
(targets-define-to double-quote "\"" nil quote)
(targets-define-to comma "," nil separator)
(targets-define-to word 'evil-word nil object)
targets-define-to
accepts additional keyword arguments. Specifying bind
as non-nil will also bind the created text objects. By default, text objects are bound to the opening delimiter (and closing delimiter for pairs, e.g i(
, i)
, a(
, a)
, etc.). If the delimiters are regexps or the type is object
, :keys
must be explicitly specified. :keys
completely replaces the default keys, and :more-keys
adds to them. Both can be either a single key or a list of keys. :inner-key
, :a-key
, :inside-key
, :around-key
, :next-key
, :last-key
, and :remote-key
can also be specified to change the intermediate keys used from their defaults. If any of them is set to nil
, the corresponding text objects will not be bound.
(targets-define-to paren "(" ")" pair
:bind t :more-keys "r" :last-key "p" :remote-key nil)
(targets-define-to word 'evil-word nil object :bind t :keys "w")
Evil does not support defining mode-local text objects with evil-define-key
(e.g. binding iw
in the operator and visual states will not override the default iw
). Buffer-local text objects do work though, so targets-define-to
provides the :hooks
keyword argument to specify hooks to be used to locally bind the defined text objects (instead of binding them globally). The argument can be a list (e.g. :hooks (emacs-lisp-mode-hook lisp-mode-hook)
) or a single hook (e.g :hooks emacs-lisp-mode-hook
). Note that the name specified should be unique from any other targets text object names (e.g. elisp-quote
if quote
already exists).
The :let
keyword is also provided to allow locally defining variables for the created text objects. See Text Object Specific Settings for more information
Composite objects are composed of multiple regular text objects. Whichever text object gives the smallest selection that includes the current selection or point will be used. If there are no text objects around the current selection or at the point, composite text objects will still seek (if targets-seek-functions
is non-nil). When seeking, the closest text object is favored. Counts still work to expand the selection multiple times (e.g. d2id
).
Here is an example of defining composite text objects that will act on any of the default pair delimiters:
(targets-define-composite-to pair-delimiter
(("(" ")" pair)
("[" "]" pair)
("{" "}" pair)
("<" ">" pair))
:bind t
:next-key nil
:last-key nil
:keys "d")
Here’s an example that creates the equivalents of the anyblock text objects:
(targets-define-composite-to anyblock
(("(" ")" pair)
("[" "]" pair)
("{" "}" pair)
("<" ">" pair)
("\"" "\"" quote)
("'" "'" quote)
("`" "`" quote)
("“" "”" quote))
:bind t
:keys "b")
targets-define-composite-to
has the same keyword arguments as targets-define-to
except there is no :more-keys
. If :bind
is specified as non-nil, :keys
must also be specified.
Not yet implemented.
Targets already supports creating pair, quote, and separator text objects directly. For more complicated text objects, targets can automatically integrate with any text objects built on top off thingatpt
(e.g. any text objects that use evil-select-inner-object
or evil-select-an-object
). Text objects that use evil-range
directly can generally be rewritten to use evil-select-*-object
instead.
The benefit of using thingatpt
is that it provides a consistent interface for the functionality needed for text object selection and seeking. It does some of the necessary work itself, and evil already uses thingatpt
for most non-pair text objects (e.g. words). That means that this section is relevant even if you don’t use this package. When a thing is implemented correctly, all the information targets needs for seeking and collecting all visible locations text objects is available. Targets can then be used to create text objects for a thing with a single line:
(targets-define-to to-name 'thing-name nil object)
;; or for text objects with the concept of "inner" vs. "outer"
(targets-define-to to-name 'inner-thing-name 'outer-thing-name object)
Implementing a thing requires implementing 1 to 5 functions:
forward-op
(required)- with a positive count, move to the nth next thing end
- with a negative count, move to the nth previous thing beginning
- don’t move the point if no (more) things
- (for evil functionality) return 0 on success and another number (e.g. 1) on failure
beginning-op
- move to the beginning of the current thing or do nothing if no thing at pointend-op
- move to the end of the current thing or do nothing if no thing at pointbounds-of-thing-at-point
- return the bounds (as a cons of the form(beg . end)
) of the current thing or nil if no thing at pointthing-at-point
(usually unnecessary; unnecessary for text objects) - return the text corresponding to the current thing or nil if no thing at point
thingatpt
does not specify what the return value of forward-op
should be, but evil requires it to return 0
on success (or 1
, for example, on failure like forward-line
does). Not all things do this, so you may need to alter the forward-op
for some things to meet this requirement (or growing a selection will break, for example).
Targets also allows implementing the following functions to support customized behavior for more specialized things (e.g. nestable things):
targets-shrink-inner-op
targets-no-extend
targets-seek-op
targets-seeks-forward-begin
targets-seeks-backward-end
targets-overlay-position
targets-extend-seek-op
See the following sections for more information on these targets functions.
thingatpt
and targets
use symbol properties to store/obtain these functions:
(put 'thing-name '<op> #'thing-name-<op>)
;; specific example
(put 'my-sentence 'forward-op #'my-sentence-forward)
;; for 'forward-op specifically, `forward-thing' will alternatively call
;; `forward-<thing-name>' if it exists
(defun forward-my-sentence ...)
thingatpt
in turn uses these functions to provide the following:
forward-thing
- with a positive count, move to the nth next thing end
- with a negative count, move to the nth previous thing beginning
- no guarunteed return value (may return nil on success)
beginning-of-thing
- move to the beginning of the current thing
- returns the beginning position on success; errors on failure (can use
ignore-errors
to get nil instead)
end-of-thing
- move to the end of the current thing
- returns the end position on success; errors on failure
bounds-of-thing-at-point
- return the bounds of the current thing or errors if no thing at pointthing-at-point
- return the text corresponding to the current thing or nil if no thing at point
forward-op
is the only function required for the previously listed functionality to work. Without a beginning-op
and end-op
, it should support both a positive and negative count. Note that there is no backward-op
; forward-op
is used for backward movement as well. With a positive count, it should move to the next thing end (which can be the end of the current thing or the end of the next thing if the point is already at the end of the current thing) that number of times. With negative count, it should move to the previous thing beginning (which can be the beginning of the current or previous thing) that number of times.
When beginning-op
and end-op
exist, thingatpt
will use them instead of forward-op
to move to the beginning and end of the current thing in order to get its bounds. Alternatively, if bounds-of-thing-at-point
exists, it will be used directly to obtain the thing bounds. Note that beginning-of-thing
and end-of-thing
always call bounds-of-thing-at-point
to get and then move to a thing’s beginning or end, so you do not need to explicitly define beginning-op
or end-op
if you’ve already implemented/defined bounds-of-thing-at-point
. In evil the point is considered as being on the next character, so if you’re implementing a text object where the point could be both at the end and at the beginning of a thing, bounds-of-thing-at-point
should return the bounds of the thing that the point is on. For an example, this is how the default list thing behaves:
(list)|
;; (bounds-of-thing-at-point 'list) returns nil
|(list)
;; (bounds-of-thing-at-point 'list) returns the bounds of list
(defun (args)|...)
;; (bounds-of-thing-at-point 'list) returns the bounds of defun
thing-at-point
returns a string corresponding to the current thing. It is generally not necessary to manually implement this function for any specific thing as thingatpt
can just use the thing bounds to get the corresponding buffer string. This functionality is also not needed for text objects.
This section isn’t strictly necessary to understand how to write a forward-op
function, but it may make it more clear how thing-at-point
uses forward-op
to obtain the bounds of a thing.
To summarize how thingatpt
finds the bounds of the current thing using only forward-op
, it will first call (forward-thing 1)
and then (forward-thing -1)
to attempt to find the beginning of the current thing. After that, it will call (forward-thing 1)
again to get the end. If that method fails, it will then try (forward-thing -1)
followed by (forward-thing 1)
to get the end (and then (forward-thing -1)
again to get the beginning). This procedure may not immediately make sense, so to briefly illustrate why this method is necessary, consider the following examples.
In the following case, (forward-thing sentence 1)
will correctly go to the end of the sentence, and (forward-thing 'sentence -1)
will correctly go to the beginning of the current sentence:
In sente|nce middle.
However, if the point is already at the sentence end, for example, (forward-thing 'sentence 1)
will move to the end of the next sentence:
At sentence end.| Next sentence. Next sentence.
;; after (forward-thing 'sentence 1)
At sentence end. Next sentence.| Next sentence.
thingatpt
can detect this failure by then running (forard-thing 'sentence -1)
:
At sentence end. |Next sentence. Next sentence.
;; point is after original position: failure
If the original (forward-thing 'sentence 1)
had moved to the end of the current sentence, (forward-thing 'sentence -1)
would have moved the point to the beginning of the current sentence, which has to either be before original position or the original position itself. Since the point is after the original position, we know this method failed and moved to the next sentence instead. However, thingatpt
can then use (forward-thing 'sentence -1)
instead to reliably move to the beginning of the current sentence. There are extra checks to handle some edge cases (e.g. the second method actually calls (forward-thing -1)
, (forward-thing 1)
, and then (forward-thing -1)
), but these are the basic steps used to get the bounds of a thing; if you want to learn more, I’d recommend looking at bounds-of-thing-at-point
directly as it is only around 50 LOC.
You can use whatever method you want, but this is my preferred way of creating new text objects. The basic process I use is as follows:
- Implement
bounds-of-thing-at-point
orbeginning-op
andend-op
(used to select the current text object) - Implement evil motions (optional)
- Implement
forward-op
(used for seeking and text object location collection) using evil motions
I prefer to implement beginning-op
and end-op
independently from forward-op
as they can potentially be useful when implementing evil motions and forward-op
. If there is not already a function to confirm that there is a thing at the point (e.g. syntax-ppss
can be used for strings/comments), you can use bounds-of-thing-at-point
once you’ve implemented it. The main thing to remember is to properly handle edge cases (stay at the current thing when at its end or beginning and don’t move the point if there is no thing at point).
It’s not necessary to implement evil motions, but it can be done without much extra work. You can implement forward-op
without motions and then create motions from the thing using, for example, evil-forward-beginning
, evil-forward-end
, evil-backward-beginning
, and evil-backward-end
(this is how evil defines a few motions; see evil-forward-section-begin
for an example). These functions make certain assumptions that aren’t necessarily always true, and I generally prefer to just implement all motions manually if it isn’t too much extra work.
Here’s an example for how you might go about implementing a forward begin motion without the thing being fully implemented (i.e. no forward-op
). This example tries to describe how to handle common edge cases, but it is not all-encompassing.
(evil-define-motion my-forward-thing-begin (count)
"Go to the next thing beginning COUNT times."
;; if should add to the jump list
;; :jump t
;; you may also want to set :type; for example, if the motion should act
;; linewise when used with an operator:
;; :type line
(or count (setq count 1))
(if (< count 0)
;; implement the backward version as a separate motion
(my-backward-thing-begin (- count))
(cl-dotimes (i count)
;; 1. save the current position in case of failure
(let ((orig-pos (point))
;; 1.1 if you are using something like `re-search-forward' and need
;; case-sensitive search, set `case-fold-search' to nil
case-fold-search
;; for recording a succesful search
successp)
;; 2. move to the end of the current thing if searching for the next
;; thing requires it (e.g. if you are implementing a string thing by
;; searching for string delimiters, you'll want to skip past the end of
;; a current string, so the search doesn't jump to the closing string
;; delimiter)
(end-of-thing 'thing-name)
;; 3. find the next thing if possible
(while (and
;; 3.1 `re-search-forward' or some dumb search not guaranteed
;; to jump to a real thing may be useful if there is not
;; already a reliable way to jump to the next thing; this should
;; fail if there are no more things after the current one
(re-search-forward "regexp" nil t)
;; 3.2 continue searching forward while the search succeeds but
;; doesn't find a real thing; quit searching when on a real
;; thing; if there is an existing function that
;; can test whether there is actually a thing at the point,
;; prefer it to using `bounds-of-thing-at-point' (e.g. it may
;; have been used when implementing `bounds-of-thing-at-point'
;; for the thing)
(not (setq successp (bounds-of-thing-at-point 'thing)))))
;; 4. end the current loop iteration
(if succesp
;; when succesful, move to the beginning of the thing; you may just
;; be able to do this with `match-beginning'; otherwise, you can
;; potentially use `beginning-of-thing' or (goto-char (car
;; successp)) if you used `bounds-of-thing-at-point' for checking in
;; step 3
(goto-char (match-beginning 0))
;; otherwise, return to the original position from the start of the
;; loop and exit the loop since there are no more things after the
;; point
(goto-char orig-pos)
(cl-return))))))
The other three motions will be fairly similar. The main differences are with regards to order and direction. For example, for a forward end motion, remember that if you aren’t already at the end of the current thing, the first iteration should move to the end of the current thing instead of to the end of the next thing.
Once you’ve implemented forward end and backward beginning functions, you can just implement forward-op
on top of them:
(defun my-forward-thing (count)
(let ((orig-pos (point)))
(if (< count 0)
(my-backward-thing-begin (- count))
(my-forward-thing-end count))
(if (= (point) orig-pos)
1
;; return 0 on success (evil has `zerop' checks; e.g. see
;; `evil-forward-not-thing')
0)))
(put 'thing-name 'forward-op #'my-forward-thing)
You can then create basic text objects without using targets like this:
(evil-define-text-object my-inner-thing (count &optional beg end type)
(evil-select-inner-object 'thing-name beg end type count))
(evil-define-text-object my-a-thing (count &optional beg end type)
(evil-select-an-object 'thing-name beg end type count))
By default, the inner and outer versions of object
type text objects are equivalent to the inside and around versions respectively (i.e. the inner version will select the thing, and the outer version will also select spaces after or before it). If you want to create a text object for a thing where it makes sense to exclude, for example, some type of delimiter for the inner version, you can create a “shrink” function to achieve this. It should take an evil range ((BEG END ...)
) and return the new evil range after shrinking the region.
For example, if you wanted to create a thing version of the paren object or any text object where the delimiters are single characters, you could define a shrink function like this:
(defun targets--shrink-inner (range)
"Shrink RANGE by 1 character on each side."
(cl-incf (car bounds))
(cl-decf (cadr bounds))
bounds)
(put 'my-thing 'targets-shrink-inner-op #'targets--shrink-inner)
Since this is common for inner text objects, you can just specify t
to use targets’ default shrink function:
;; use default shrink function
(put 'my-thing 'targets-shrink-inner-op t)
Note that you don’t need to check if the range is non-nil or if the new range is valid (i.e. the beginning is not equal to or after the end); targets already handles these cases.
This is the equivalent without using targets (evil-select-inner-object
should be used for both):
(evil-define-text-object my-inner-thing (count &optional beg end type)
(let* ((range
(evil-select-inner-object 'my-thing beg end type count))
(new-range (when range
(my-shrink-thing range))))
(if (and new-range
(< (car new-range) (cadr new-range)))
new-range
range)))
(evil-define-text-object my-a-thing (count &optional beg end type)
(evil-select-inner-object 'my-thing beg end type count))
For some things (particularly non-nestable things), extending an active region/visual selection does not make sense. For example, if you were to write a string text objects using thingatpt
, region extension would act like this:
(~"string|" (foo (bar "string")))
;; press the key for "a string" again
(~"string" (foo (bar| "string")))
Furthermore, if you were to attempt to delete a string in between strings, evil would delete the region in between strings instead of seeking. As it is unlikely for this behavior to be useful, you can prevent it by setting the targets-no-extend
symbol property:
(put 'my-thing 'targets-no-extend t)
Now targets will seek forward in cases where there is no thing at the point or the same object is already selected. For example:
(~"string|" (foo (bar "string")))
;; press the key for "a string" again
("string" (foo (bar ~"string|")))
Default forward functions like forward-sexp
do not enter into nested lists but instead stay at the same level. Using a thing with this behavior will work fine with this package, but if you want seeking/remote operations to work with things at all nesting levels, you may wish to create a forward function that will enter and exit nested things. To extend/improve behavior of seeking, you can implement the following functions:
targets-seek-op
targets-seeks-forward-begin
(see Bounds of Thing at Point Ambiguity)targets-seeks-backward-end
(see above)targets-overlay-position
(usually unnecessary)
A forward-op
can be written to enter and exit nested things, but its default behavior (i.e. going to the end of a thing) is not suitable for seeking. Consider the following example:
;; "l" is being used for a list thing
(|foo (bar (baz)))
;; dinl
(foo (bar (|)))
In this example, you might expect dinl
to delete bar...
, but it instead deletes baz...
. This is because forward-op
normally seeks to the next thing end not beginning. While you could simply write your forward-op
so that it moves forward to thing beginning instead, targets provides targets-seek-op
for this purpose instead (since targets, evil, or another package may want to rely on forward-op
going to a thing’s end). targets-seek-op
should follow these rules:
- With a positive count, move to the thing a “next” text object should act on
- With a negative count, move to the thing a “previous” text object should act on
- Do not move the point on failure
- Return value does not matter
Here are some possible behaviors for targets-seek-op
:
- Go to the next thing beginning with a positive count or to the previous thing beginning with a negative count (
targets-overlay-position
unnecessary) - Go to the next thing beginning or end with a positive count or to the previous thing beginning or end with a positive count (
targets-overlay-position
required to consistently display the overlay at the start) - Go to the next thing beginning with a positive count or to the previous thing end with a negative count (opposite of
forward-op
; probably just as undesirable since it will be impossible to have next/previous text objects work on things that start before the point or end after the point)
With 1, seeking will have this behavior:
(foo (bar (|baz)) (qux))
;; dinl
(foo (bar (|baz)) ())
With 2, dinl
will delete the next list, even if it started before the point:
(foo (bar (|baz)) (qux))
;; dinl
(foo (|) (qux))
I personally prefer 1 since it consistently acts on opening delimiters and is easy to implement, but you can implement targets-seek-op
however you want. You could even create alternate text objects for both behaviors if you wanted to.
targets-overlay-position
is used when collecting text object locations during remote selection to obtain the positions to put overlays at for each thing. It should follow these rules:
- The function takes no arguments and should move the point to the location to put the overlay; the point is guaranteed to be on a thing initially
- The return value doesn’t matter
For example, if targets-seek-op
sometimes or always moves the point to a thing end, and you only want avy overlays to appear on thing beginnings, you could implement targets-overlay-position
like this:
(defun my-thing-overlay-position ()
(beginning-of-thing 'my-thing))
(put 'my-thing 'targets-overlay-position 'my-thing-overlay-position)
By default, targets will use the current thing beginning as the overlay position, so you do not actually need to implement targets-overlay-position
unless you want overlays to sometimes or always be at the end of a thing (or some other position).
Also note that targets will automatically sort the collected positions, so it doesn’t matter if the overlay positions are not found in order.
Evil’s object/thing selection functions are not suitable for extending a visual selection for nestable things. If you want repeatedly calling the text object in visual state to expand the selection to the outer thing, you can implement targets-extend-seek-op
to have the following behavior:
- Takes no arguments
- Should move the point to the location where
bounds-of-thing-at-point
should be called (e.g. move the point to the outer thing) - Should return non-nil on success; should error or return nil on failure (in which case targets will seek if seeking is enabled)
For example, implementing region for a list thing would look like this:
(defun my-up-list ()
"Like `up-list' but return non-nil on success."
(let ((orig-pos (point)))
(ignore-errors (up-list))
(unless (= (point) orig-pos)
(point))))
(put 'my-list-thing 'targets-extend-seek-op #'my-up-list)
You can probably ignore this section unless you are having issues with the wrong thing being selected when the point is bordering two things. Targets is smart enough to handle most cases where there is a thing on either side of the point automatically, but for some things, you may need to keep this possibility in mind when implementing bounds-of-thing-at-point
(or beginning-op
and end-op
).
As previously mentioned, evil considers the point to be on the character after it. Consider the following example for an evil-word
:
foo|-bar
In this example, the point is at the end of the word foo
and at the beginning of the hyphen. Since the point is considered on the hyphen, there is no ambiguity. diw
will act on the hyphen. However, now consider seeking. forward-op
will move to the next thing if it as at the end of a thing. Targets can’t always move to the end of a thing and then seek since this could skip over nested things (this is why targets-seek-op
exists). Since forward-op
(or targets-seek-op
) may not move the point to the next thing, targets has to check bounds-of-thing-at-point
to see if the thing as changed. For most cases this works as expected:
|foo bar
;; (forward-thing 'evil-word)
foo| bar
;; (bounds-of-thing-at-point 'evil-word) returns bounds of foo
This won’t work when things are directly next to each other though:
|foo-bar
;; (forward-thing 'evil-word)
foo|-bar
;; (bounds-of-thing-at-point 'evil-word) returns bounds of -; not what we
;; wanted!
Targets handles this by checking the bounds at the character before the point when seeking to the end of a thing (this also handles cases where bounds-of-thing-at-point
returns nil at the end of a thing, e.g. the default list thing).
As for nested text objects, targets can handle both of the common cases where the point is at two things:
;; at end of thing, use previous character's bounds
(foo (bar)|)
;; not at end of thing, don't use previous character's bounds
(|(foo) bar)
For a nestable thing with a custom targets-seek-op
, targets may not correctly handle the following case since seeking could potentially have moved the point either to the end of foo or to the beginning of bar:
(foo)|(bar)
This case is unlikely because there will generally be a space in between lists, but if this is a possible issue for your thing (e.g. a thing end can be right next to a beginning and targets-seek-op
may go to the beginning of a thing with a positive count), then you must let targets know how your custom seek operation behaves by setting targets-seeks-backward-end
and/or targets-seeks-forard-begin
:
;; this seek operation always seeks to the thing end (for both directions)
(put 'my-thing 'targets-seek-op #'my-thing-custom-seek)
;; let targets know
(put 'my-thing 'targets-seeks-backward-end t)
;; this seek operation always seeks to the thing beginning (for both directions)
(put 'my-thing 'targets-seek-op #'my-thing-new-custom-seek)
;; let targets know
(put 'my-thing 'targets-seeks-forward-begin t)
This command will run the last text object used in the current state (operator or visual). Note that this only works for text objects defined with targets.el. For operator state, it may be useful if you want to use a different operator with the previous text object (otherwise you could just use evil-repeat
). It is probably more useful for visual state where it can be used as a shorter key to expand the region. The last text object for visual state resets in between visual selections. You can set targets-default-text-object
to a default text object to use the first time targets-last-text-object
is run after visual state is entered.
targets-last-text-object
is unbound by default; I personally bind it to RET
:
(define-key evil-visual-state-map (kbd "RET") #'targets-last-text-object)
(define-key evil-operator-state-map (kbd "RET") #'targets-last-text-object)
targets-setup
can be used to create and optionally bind all the text objects specified in targets-text-objects
, targets-user-text-objects
, and targets-composite-text-objects
. Each is a list of lists of arguments to be passed to targets-define-to
(or targets-define-composite-to
in the case of targets-composite-text-objects
). Entries in targets-user-text-objects
that have the same name as a default text object in targets-text-objects
are given precedence. This allows easily overriding any of the default text objects. There are no default composite text objects.
Please note that if you do not use targets-setup
, you will need to run (add-hook 'post-command-hook #'targets--post-command)
for jump list and position resetting functionality to work correctly. In case it does other necessary setup in the future, it is recommended that you use it even if you do not wish to create/bind text objects with it.
When run without any arguments, targets-setup
will only create the text objects. It takes an optional, positional argument that specifies whether text objects should also be bound to keys. Keyword arguments can be used to customize the keys used in the bindings. :inside-key
and :around-key
determine what keys are bound to targets-inside-text-objects-map
and targets-around-text-objects-map
in the visual and operator states. They default to I
and A
respectively. If they are not changed from their defaults, they will be bound in a way such that I
and A
will continue to work as normal with a visual block selection.
inner-key
, a-key
, :next-key
, :last-key
, and :remote-key
can also be specified; they will be passed to targets-define-to
.
(targets-setup t :last-key "L" :around-key (kbd "C-a"))
;; don't bind remote text objects
(targets-setup t :remote-key nil)
Note that all of the *-key
keywords and :bind
can be overridden for an individual entry in targets-text-objects
or targets-user-text-objects
. targets-text-objects
is composed of targets-pair-text-objects
, targets-quote-text-objects
, targets-separator-text-objects
, and targets-object-text-objects
. If you would like to completely modify the default text objects, you can also set any of these before loading targets.
(setq targets-quote-text-objects
'((single-quote "'" nil quote :next-key "N")
(double-quote "\"" nil quote :last-key "L")
(smart-single-quote "‘" "’" quote :bind nil)
...))
After targets has loaded, you can still add items to and remove items from targets-text-objects
, targets-user-text-objects
, and targets-composite-text-objects
before running targets-setup
.
(use-package targets
:load-path "path/to/targets.el"
:init
(setq targets-user-text-objects '((pipe "|" nil separator)
(paren "(" ")" pair :more-keys "b")
(bracket "[" "]" pair :more-keys "r")
(curly "{" "}" pair :more-keys "c")))
:config
(targets-setup t
:inside-key nil
:around-key nil
:remote-key nil))
The :let
keyword can be used to locally bind certain variables for all the text objects created by a single targets-define-to
or targets-define-composite-to
statement:
(targets-define-to paren "(" ")" pair
:let ((targets-bound #'my-targets-paren-bound)))
targets-settings-alist
can also be set to locally bind certain variables for specific text objects (matched by the exact symbol or a regexp). These bindings will override those created with :let
. At the moment, only the bindings for the symbol or regexp that is matched first will be used.
(setq targets-settings-alist
'((targets-inner-paren
((targets-bound #'my-inner-paren-bound)))
("^targets-[[:alpha:]]+-remote"
((targets-bound #'my-smaller-bound)))))
Some operators should move the point even when used with next, last, or remote text objects (e.g. evil-change
). To prevent position resetting with these operator, you can customize evil-change-commands
(if your custom change operator is not already in it) or targets-no-reset-operators
.
For remote text objects, the user can change targets-avy-style
, targets-avy-keys
, targets-avy-background
, targets-avy-all-windows
, and targets-avy-all-windows-alt
. All will override the corresponding avy settings when set by the user. By default, they are not bound, and the values of the corresponding avy settings are used. Note that you can also use avy-keys-alist
and avy-styles-alist
for customizing the behavior of specific text objects.
Although targets-avy-all-windows
and targets-avy-all-windows-alt
exist, changing them is not recommended. Using remote text objects with more than one window is not fully supported (and not all that useful). While it will work to create a visual selection, it will not work with other operators unless the other window is for the same buffer. I have not found a way around this at the moment.
- wellle/targets.vim vim plugin
- rhysd/vim-textobj-anyblock vim plugin
- paradigm/TextObjectify vim plugin
- expand-region.el emacs package and terryma/vim-expand-region vim plugin
- this reddit thread