Swap two characters atomically, so that it can be repeated with "." - vim

The idiom for swapping two characters in Vim is xp.
See the help at :h 04.5, final paragraph, and Vim: how do I swap two characters?.
But very occasionally I need to do two or more swap operations in a row. I'm often surprised that what feels like a single operation to me, xp, is actually two operations to Vim. This makes it impossible to repeat xp with .. (Or atomic undo with u.)
Is there a way to swap two characters in a single operation that I can quickly repeat with .?

This isn't ideal, but if you store xp as a macro, it will be repeatable after you've executed the macro. So instead of xp, you're using, say #s (for swap). Then ## will repeat it. Not as nice as ., but it does work.
Edit: You know, I'm sure there's a way to accomplish this with some vimscript and tpope's repeat.vim. Sadly, my vimscript is not up to snuff. I got this far - maybe someone can correct where I'm going wrong?
fun! DoSwap()
:normal xp
silent! call repeat#set("\<Plug>Swap",1)
endfun
nnoremap <silent> <Plug>Swap :call DoSwap()<CR>
nmap <Leader>s <Plug>Swap
The problem with this is that once you enter \s, it does indeed swap the characters you're on, and it DOES repeat when you hit ., but it jumps to the beginning of the line first, which is not what you want. But I do think this can be done, I'm just not all the way there.

Here's an idea. Suppose the cursor is on the "y" of the word "oyxgen".
In normal mode, type
2sxy<Esc>
Now the word reads "oxygen". Find the next instance of the typo (fy) and repeat the change (.).
Too bad this works only for the specific swap "yx" to "xy", so this isn't the definitive solution.

Vimcasts.org has published an episode that addresses this very problem.
As #Jeremy guessed the solution is to use the repeat.vim plugin. Then simply add the following <Plug> mapping and key mapping to your vimrc.
nnoremap <silent> <Plug>Transpose xp:call repeat#set("\<Plug>Transpose")<CR>
nmap cp <Plug>Transpose
The mapping shown here uses cp. cp is the new xp, repeatable with . but otherwise exactly the same.
Now all that remains is getting used to it.

Related

Delete specific lines above current line in vim normal mode? [duplicate]

I have a mapping in my vimrc that downwardly comments out regions of c code:
nmap comc :normal! I//<ESC>
Since the 'normal' ex command implicitly converts input such as "Ncomc" to ".,.+N-1 comc", I can range comments downwardly without many keystrokes and without leaving normal mode. This is, however, a very limited subset of what vim ranges can do. If I'm willing to be verbose, I can achieve upward ranging comments like so:
.,.-5 normal comc
While editing text, I would much prefer to type something like "-6comc" or make a mapping of "Comc" that uses upward ranges. I'm haven't been able to do so successfully.
Similarly, range operations support commenting until a search pattern is reached, e.g :
.,/int main/ comc
I would, however, like to do so without all that typing.
The behavior you requested is normally done with :h map-operator mapping. With this commenting 3 lines down will turn into comc2j though, but 3 lines up is now just as easy: comc2k.
You can also use visual mode without changing your mapping at all: V2kcomc. You will have to add xnoremap with the identical lhs and rhs because nnoremap works only for normal mode. (And do not use nmap.)
Third option is mapping - to something that moves {count} lines up and puts count back:
nnoremap <expr> - (v:count ? ":\<C-u>\n" . (v:count-1) . 'k' . v:count : '')
. This assumes you are writing 6-comc, not -6comc.
// By the way, I would suggest The NERD Commenter if it comes to the plugin.
While it's commendable to go as far as possible without any plugins, sometimes they're just the best option. What will you do when you start working in a language that has comments with # or (*...*)? Add new mappings for these comment characters?
I recommend commentary.vim which does filetype-aware commenting.
The default commenting operator in commentary.vim is gc. You can combine it with motions, and use it in Visual mode too.
Your use cases:
Comment downwards N lines (say, 3): :.,.+3normal gcc, or gc3j or 4gcc.
Comment upwards 5 lines: :.,.-5normal gcc, or simply gc5k.
Comment until int main: :.,/int main/-1normal gcc, or simply gc/int main followed by Enter.

Insert character without entering insert mode?

Sometimes I want to insert a # to comment out a line and test it quickly. Currently I do:
i#ESC:w
Is there something shorter I can do?
Although I agree with others that there are better ways to comment and uncomment code, it seems that people have gotten distracted and forgotten to actually answer the question.
This is my approach to inserting a single character:
:noremap <key> i <Esc>r
I tend to find that I need to replace, remove, or add single characters very often if I'm correcting typos, so (resp.) r, x, and whatever is chosen for <key> in the above become very handy.
Note that . is also particularly handy for this sort of task. It repeats the previous action.
Personally though, I only map this function to a valuable key when I'm doing a task where I use it frequently enough to justify occupying a prime spot on the keyboard (such as correcting typos), because really, it only saves one keystroke per use and that's only when <key> is not a combination, which of course limits availability.
I map a couple of things to my <leader> key (\ by default):
" # comment the current line
nnoremap <leader>d I#<ESC>
" block comment in visual mode
vnoremap <leader>c <ESC>'<O/*<ESC>'>o*/<ESC>V'<k
If you want to add a # to the start of a group of lines, then do this:
<ctl-v>
j (as many times as necessary
I#
<esc>
You could use a recording. From normal mode, type:
qlml0i#<press escape>`lq
Then to comment out a line, just press #l
Mapping in vim is so easy that I might do something like
:nmap CC I#<Esc>:w<CR>
on the fly. If I get used to it, then I will add it to my vimrc file.
:help key-mapping
:help usr_40.txt
Actually there is a plugin you might wanna take a look at:
http://www.vim.org/scripts/script.php?script_id=1218
It is specifically designed for that purpose.
I'm particularly fond of the tComment plugin. gcc to comment a line, repeat to uncomment, multiple lines, motions, etc.

Advanced Usage of Ranges with Vim Keymappings

I have a mapping in my vimrc that downwardly comments out regions of c code:
nmap comc :normal! I//<ESC>
Since the 'normal' ex command implicitly converts input such as "Ncomc" to ".,.+N-1 comc", I can range comments downwardly without many keystrokes and without leaving normal mode. This is, however, a very limited subset of what vim ranges can do. If I'm willing to be verbose, I can achieve upward ranging comments like so:
.,.-5 normal comc
While editing text, I would much prefer to type something like "-6comc" or make a mapping of "Comc" that uses upward ranges. I'm haven't been able to do so successfully.
Similarly, range operations support commenting until a search pattern is reached, e.g :
.,/int main/ comc
I would, however, like to do so without all that typing.
The behavior you requested is normally done with :h map-operator mapping. With this commenting 3 lines down will turn into comc2j though, but 3 lines up is now just as easy: comc2k.
You can also use visual mode without changing your mapping at all: V2kcomc. You will have to add xnoremap with the identical lhs and rhs because nnoremap works only for normal mode. (And do not use nmap.)
Third option is mapping - to something that moves {count} lines up and puts count back:
nnoremap <expr> - (v:count ? ":\<C-u>\n" . (v:count-1) . 'k' . v:count : '')
. This assumes you are writing 6-comc, not -6comc.
// By the way, I would suggest The NERD Commenter if it comes to the plugin.
While it's commendable to go as far as possible without any plugins, sometimes they're just the best option. What will you do when you start working in a language that has comments with # or (*...*)? Add new mappings for these comment characters?
I recommend commentary.vim which does filetype-aware commenting.
The default commenting operator in commentary.vim is gc. You can combine it with motions, and use it in Visual mode too.
Your use cases:
Comment downwards N lines (say, 3): :.,.+3normal gcc, or gc3j or 4gcc.
Comment upwards 5 lines: :.,.-5normal gcc, or simply gc5k.
Comment until int main: :.,/int main/-1normal gcc, or simply gc/int main followed by Enter.

Making inserting a single character in Vim an atomic operation

I've long used this very useful shortcut in vim:
nmap <space> i <esc>r
this means that if I press spacef, for example, it will insert a single character f at the given position.
unfortunately, however, this is not atomic, ie, if I press spacef and then navigate somewhere else, then press ., I get the equivalent of rf, not spacef.
all this makes sense, but here's the question: is there a way of making this atomic, so that . will repeat the 'insert character' operation, and so that undo etc all treat it as one operation, too?
Awesome! Michael's answer pointed me to the plugin I needed to finish my plugin, which can now do what you want - I had been trying to figure out how to do this for ages!
1) Install Tim Pope's plugin
2) Install my plugin
3) Add a mapping to your .vimrc:
nnoremap <space> :<C-U>call InsertChar#insert(v:count1)<CR>
Does this work for you?
noremap <silent> <space> :exe "normal i".nr2char(getchar())<CR>
You might want to have a look at this plugin script. It may be possible to configure your map so it can be supported. Read the supporting docs
http://www.vim.org/scripts/script.php?script_id=2136
Sorry I can't provide a specific answer to your problem but I will note that I tend to use the . key when I have to reproduce quite a lot of commands (e.g. I want to insert f 5 or more times).
If that is the case here, I don't think the saving of using your macro is worth it. You save one keystroke by using your macro rather than ifesc and that operation is atomic so you could then . to your heart's content.
I would just use the non-macro version if I know I want to repeat it a lot.
P.S. You know I'm starting to like the <kbd> tag quite a bit :-)

How to emulate Emacs’ transpose-words in Vim?

Emacs has a useful transpose-words command which lets one exchange the word before the cursor with the word after the cursor, preserving punctuation.
For example, ‘stack |overflow’ + M-t = ‘overflow stack|’ (‘|’ is the cursor position).
<a>|<p> becomes <p><a|>.
Is it possible to emulate it in Vim? I know I can use dwwP, but it doesn’t work well with punctuation.
Update: No, dwwP is really not a solution. Imagine:
SOME_BOOST_PP_BLACK_MAGIC( (a)(b)(c) )
// with cursor here ^
Emacs’ M-t would have exchanged b and c, resulting in (a)(c)(b).
What works is /\w
yiwNviwpnviwgp. But it spoils "" and "/. Is there a cleaner solution?
Update²:
Solved
:nmap gn :s,\v(\w+)(\W*%#\W*)(\w+),\3\2\1\r,<CR>kgJ:nohl<CR>
Imperfect, but works.
Thanks Camflan for bringing the %# item to my attention. Of course, it’s all on the wiki, but I didn’t realize it could solve the problem of exact (Emacs got it completely right) duplication of the transpose-words feature.
These are from my .vimrc and work well for me.
" swap two words
:vnoremap <C-X> <Esc>`.``gvP``P
" Swap word with next word
nmap <silent> gw "_yiw:s/\(\%#\w\+\)\(\_W\+\)\(\w\+\)/\3\2\1/<cr><c-o><c-l> *N*
Depending on the situation, you can use the W or B commands, as in dWwP. The "capital" versions skip to the next/previous space, including punctuation. The f and t commands can help, as well, for specifying the end of the deleted range.
There's also a discussion on the Vim Tips Wiki about various swapping techniques.
In the middle of a line, go to the first letter of the first word, then do
dw wP
At the end of a line (ie the last two words of the line), go to the space between the words and do
2dw bhP
From the handy Equivalence of VIM & Emacs commands
You could add shortcut keys for those by adding something like the following to your vimrc file:
map L dwwP
map M 2dwbhP
In that case, SHIFT-L (in command-mode) would switch words in the middle of the line and SHIFT-M would do it at the end.
NB: This works best with space-separated words and doesn't handle the OP's specific case very well.
There's a tip on http://vim.wikia.com/wiki/VimTip10. But I choose to roll my own.
My snippet has two obvious advantages over the method mentioned in the tip: 1) it works when the cursor isn't in a word. 2) it won't high-light the entire screen.
It works almost like emacs 'transpose-words', except that when transposition is impossible, it does nothing. (emacs 'transpose-words' would blink and change cursor position to the beginning of current word.)
"transpose words (like emacs `transpose-words')
function! TransposeWords()
if search('\w\+\%#\w*\W\+\w\+')
elseif search('\w\+\W\+\%#\W*\w\+')
endif
let l:pos = getpos('.')
exec 'silent! :s/\(\%#\w\+\)\(\W\+\)\(\w\+\)/\3\2\1/'
call setpos('.', l:pos)
let l:_ = search('\(\%#\w\+\W\+\)\#<=\w\+')
normal el
endfunction
nmap <silent> <M-right> :call TransposeWords()<CR>
imap <silent> <M-right> <C-O>:call TransposeWords()<CR>
You can use dwwP or dWwP as Mark and CapnNefarious have said, but I have a few notes of my own:
If the cursor is on the first letter of the second word, as in the example you gave, you can use dwbP (or dWbP to handle punctuation);
If the cursor is in the middle of the word, you can use dawbP/daWbP.
There's a transpose-words script on vim.org that works beautifully.

Resources