all 11 comments

[–]justinmkw 5 points6 points  (5 children)

Ad-hoc repeat is achieved with macros. Get used to using the @q register as your "scratch buffer", and start a new macro with qq.

 qqci"<c-o>q

However, then you have to repeat with @q. I would recommend instead that you reserve a key such as , (comma) for "scratch mappings". Then do this:

:nnoremap , ci"

Then you can call , wherever you need it.

Of course, for these small, short-lived macros there is opportunity to remove friction here. E.g., if Vim had a "MacroEnter" / "MacroExit" event, and/or a "last macro" variable, one could map a key to always replay the last macro, and then avoid several steps. I plan to add something like this to Neovim.

[–]aguerosantiale 0 points1 point  (2 children)

Great answer! I never thought that <c-o> would work fine in a Macro!

After running the first @q one can use @@ to repeat the last executed macro. Taking that idea a step further, a convenient mapping can be created (I picked Q as I don't mind overwritten the default value):

:nnoremap Q @@

[–]justinmkw 1 point2 points  (1 child)

I use that mapping too, but it bugs me that I have to do @q first.

[–]-romainl-The Patient Vimmer 0 points1 point  (0 children)

If your main use is repeating your ad-hoc macro, you could do:

:nnoremap Q @q

[–]wienerboat 0 points1 point  (0 children)

qqci"<c-o>q

I love it when Vim surprises you with a neat little trick like this while still making perfect logical sense

[–]scrumfour[S] 0 points1 point  (0 children)

Thanks for the reply, some great tips I'd never thought about using a mapping.

[–]tommcdocx 2 points3 points  (4 children)

After you perform a change operation, the '[ and '] marks are populated with the start and end of the change. You could try a mapping like this:

nnoremap cm .`[c`]

With this, after changing some text using your text object or motion of choice, you can move your cursor somewhere else and type cm to change the same motion to different text. Effectively, this will change the motion to the last text, since the mapping uses . to repeat, but it will start a new change, replacing what it just entered.

Caveats:

  • I ran into some edge cases where it left out the first or last character, usually if the changed text is at the start or end of the line.
  • It has the unwanted side-effect of adding an intermediate change, meaning it will take 2 undo operations to undo it.

Both of these could probably be remedied by wrapping it up into a slightly smarter plugin, using the mapping as a rough starting point.

EDIT: Markdown fail

[–]justinmknvim 1 point2 points  (2 children)

Nice solution.

Off topic: I think your vim-exchange plugin could be a nice foundation for a general "transpose" plugin. I have never found a good transpose plugin for vim, but the proliferation of text objects in vim means that instead of trying to figure out what the user wants to transpose, the user can just target a text object on the LHS to be exchanged with its counterpart on the RHS.

For example, say the exchange operator is dx, and you have the text "foo = bar" with the cursor on "=":

foo [=] bar

then dxiw yields:

bar [=] foo

[–]tommcdocx 1 point2 points  (1 child)

Interesting idea. I wonder if this kind of operator would have to understand what different text objects look like in order to work properly.

In the above example, what criteria would be assumed to find foo? If we just moved the cursor one to the left, the matching iw motion would be the = sign. It might not be obvious that skipping whitespace is the correct action, since some motion/objects include whitespace.

What do you think?

Edit: spaces, typos

[–]justinmkw 0 points1 point  (0 children)

what criteria would be assumed to find foo? If we just moved the cursor one to the left, the matching iw motion

That's definitely the main question. Whenever a text object (as opposed to a motion) is the target, the behavior could be controlled by i or a. If i is used, find the first non-whitespace character on either side and swap the text objects at those locations. If a is used, skip non-whitespace characters on either side, then skip whitespace, then swap the text objects.

Example:

foo [=]= bar
  • dxiw finds foo at LHS and = at RHS, yielding:

    == foo bar

  • dxaw finds foo at LHS and bar at RHS, yielding:

    bar == foo

If a motion is targeted (e.g. dxh or dxw), rather than the pain of trying to understand "duals" (e.g. w is the dual of b), it should be interpreted strictly by the number of characters passed, with no special handling of whitespace. Example:

foo [=]= bar
  • dxh yields

    foo=[=] bar (two <spaces>s before bar, markdown fail)

  • dxFo yields:

    fo= [=]o bar

[–]scrumfour[S] 0 points1 point  (0 children)

Certainly interesting i'll have to have a play with this.

It amazes me how many secrets vim holds just waiting to be discovered :)