From my research into this, you need to define at least two custom motions. The first will be for just moving the cursor, and the second will be for use in operators. For a full functioning setup, it is much more complicated, and I would recommend looking at the code in CamelCaseMotion by Ingo Karkat which shows significantly more thought on doing this right than I do below. Most of my answer is based on quick information found in Vim help and experimentation.
In the following examples, I'm going to remap the )
and (
motions to do the each end of a sentence. You can use other characters as you wish by modifying the example. I'm also going your example of vis<ESC>
as the more robust option (compared to )T.
). In doing so, if you are already at the end of a sentence, you end up staying at the end instead of moving to the end after this one, because of the way is
works. You can try replacing it with as
for different results. This should actually work for turning either side of a text object into a motion.
First, the normal mode movement
Note This doesn't work in visual mode or operator pending mode
function! MoveToEnd(type, ...)
exe "normal `]"
endfunction
map <silent> \) :set opfunc=MoveToEnd<CR>g@
nmap ) \)is
First, we define a new function called MoveToEnd
whose sole job is to put the cursor at the the "last character of the previously changed or yanked text", which is the ]
mark (see :help ']
).
Next, we create a custom operator \)
that calls our MoveToEnd
function on whatever motion is given to it. In this way, the ]
mark becomes the end of the selection given to our new operator. (see :help map-operator
)
Finally, we map )
to call our custom operator with the is
mostion, thereby effectively giving the equivalent of vis<ESC>
without changing the last visual selection and without changing modes.
The reverse is similar, just using the other directions:
function! MoveToBeginning(type, ...)
exe "normal `["
endfunction
map <silent> \( :set opfunc=MoveToBeginning<CR>g@
:nmap ( \(is
Second, the operator movement
This one is trickier. Our motion above destroys the start position, so operators like 'd' end up working against nothing. What we need is to be able to save our start position and add the end position. :help omap-info
(operator pending mapping) gives a tip on doing this, which is to actually use visual mode.
What we'll do then, is save our position to one end of the visual selection register, use our normal mode motion from above, set the other visual selection register, and then activate visual mode:
function! SelectToEnd()
normal m< "Set start point of visual selection
normal ) "Move to end of sentance (relies on mapping from earlier)
normal m> "Set end point of visual selection
normal gv " 'Go Visual' activate visual mode using selection marks
endfunction
omap ) :call SelectToEnd()<CR>
And for our back motion:
function! SelectToBeginning()
normal m> "Set end point to current position (since we are moving backwards)
normal ( "Move to beginning of sentence (relies on mapping from earlier)
normal m< "Set start point
normal gv "Activate visual mode based on our new marks
endfunction
omap ( :call SelectToBeginning()
With this, you can do d)
to delete to the end of the sentence (instead of the original of deleting to the beginning of the next one). All other operators should work as well.
<esc>
s? You only need one.)gE
. I know you said you always end sentences with a period, but it's better to not even have to assume that.as
andis
do not only work in visual mode. You can dodas
in normal mode to Delete A Sentence.