5

In normal mode ( and ) move to the previous or next first character in the sentence, respectively. How do I move to the very last character.

If all of my sentences end with a period I can use )T. (first character of the next sentence, then backwards to the first period) but I want a more robust solution to use in a macro.

I know about as and is while in visual mode, so vis<ESC> works (visual mode, then select inside the sentence, then exit visual mode), but that seems pretty ugly to me, and I can't use it as a motion with other commands.

I'm at a loss. Help!

  • Why two <esc>s? You only need one. – Doorknob Feb 26 '15 at 0:51
  • Oops, I was just getting mixed up because of the delay for vim to respond. I'll edit the question. – bsmith89 Feb 26 '15 at 0:56
  • 1
    I can only suggest )gE. I know you said you always end sentences with a period, but it's better to not even have to assume that. – superjer Feb 26 '15 at 1:35
  • Also note that as and is do not only work in visual mode. You can do das in normal mode to Delete A Sentence. – superjer Feb 26 '15 at 1:39
9

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.

4

Ninja Feet

I wrote a plugin called ninja-feet.vim that aims to help with this problem.

Motiony Operators

It defines some motiony operators1 that allow you to operate on text from your cursor to one end of a text object. Using [ or ] between the operator and text object causes the operator to operate on text from the cursor to the left or right (respectively) boundary of the text object.

There are also Normal mode operators for starting Insert mode at the beginning or end of a text object: z[ and z], respectively.

Example 1: Change from here to the end of the string

In this example, let | denote the cursor position.

foo("This is a string| with superfluous text at the end", bar);

Typing d]i" will change this to:

foo("This is a string", bar);
  1. d: use the delete operator
  2. ]: to the end of the following text object
  3. i": quoted string text object

Example 2: Start inserting after the current sentence

Again, let | denote the cursor position.

Here is |some sentence. And here is another.

Typing z]is will start Insert mode at the end of the first sentence.

  1. z]: start Insert mode after the following text object
  2. is: sentence text object

Caveats

  • These mappings are not repeatable with .. It's quite tricky and I'm trying to get it right.
  • The [ and ] operators (used between operator and text object) only work for text objects that begin with i or a. Technically, that's because they're really maps for [i, ]i, [a and ]a. I would love to improve this, but it's also very tricky.
  • There are no Normal mode motions to simply move your cursor to an end of a text object. I have gotten it to work, but at the price of the motion actually being an operator (meaning . will repeat the motion). I don't find that behaviour acceptable.

1 I call them motiony operators because they're operators mapped in Operator-pending mode, which is the mode you're in between typing an operator and a motion (e.g. between the d and w in dw).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.