Transpose the last 2 words
huyz opened this issue · 5 comments
What would be a reliable mapping to transpose the last 2 words, something that works consistently even if words are only one character long?
Hello, thanks for reaching out!
Can you provide some examples of what you're trying to achieve? Sample text and expected result would be very helpful.
Sure.
If I have first second third
and the mode is normal and the cursor is on any character of second
(or, preferably but not required, on the whitespace right after second
), then if I hit a key sequence, it would be swap first
and second
and the cursor would end on the last character of first
.
In insert mode, the cursor could be anywhere before, in between, or after the characters of second
and the key sequence would trigger a swap of first
and second
and the cursor would end up after last character of first
and I would still be in insert mode.
This is what I have so far:
" Swap words using vim-exchange
nmap gX cxiWBcxiWEE
" <A-x> on macOS keyboard
imap ≈ <Esc>gXa
but the cursor ends up in the wrong place if a word is only one character long.
You could set a mark (e.g. ma
to set to "a" mark) and return to it after (e.g. `a
)
nmap gX macxiWBcxiW`a
Also, a little tip: When you're exchanging with the same region, you can use .
(repeat) the second time:
nmap gX macxiWB.`a
Caveat
The biggest caveat to this is that you might be clobbering a mark that you've previously set. Avoiding that is a bit more involved - you can use the setpos()
and getpos()
functions, but to do so you'd need to put your mapping into a function.
function! s:exchange_two_words()
let l:pos = getpos('.')
normal cxiWB.
call setpos('.', l:pos)
endfunction
nmap gX :<C-u>call <SID>exchange_two_words()<CR>
I tested this a bit, but you might want to do some more thorough testing.
Thanks, that was very helpful.
I had to tweak that to make sure that it worked if the second word was one character long. I don't know the idiomatic way of doing "press E
unless already at the last character of a word", but I realized that visual mode does move the cursor that way.
function! s:swap_last_two_words()
" First, go to the last character of last word, even if the Word is one character long
normal viW
let l:pos = getpos('.')
normal XBcxiW
call setpos('.', l:pos)
endfunction
nnoremap gs :<C-u>call <SID>swap_last_two_words()<CR>
if has("gui_running") && has("mac")
" <M-s> on macOS
nmap ß gs
imap ß <Esc>gsa
else
nmap <M-s> gs
imap <M-s> <Esc>gsa
endif
There is at least one edge case if the words straddle two lines, but this is largely enough.
Also, a little tip: When you're exchanging with the same region, you can use . (repeat) the second time:
nice!
I wanted to make this work in insert mode even after whitespace (and even if at the end of the line, which is hard to deal with). Finally have the following (which is a lot more complex than I expected):
" Usage: cursor must be on any character of the second word
function! s:swap_last_two_words()
" First, go to last character of last word, even if the Word is one long character
normal viW
let l:pos = getpos('.')
normal XBcxiW
call setpos('.', l:pos)
endfunction
" Usage: cursor must be right before second word, inside the word, or after
" any whitespace after the word
function! s:swap_last_two_words_in_insert_mode()
let l:addone = 0
let l:beyondeol = 0
let l:pos = getpos('.')
if col(".") == col("$")
let l:beyondeol = 1
endif
normal \<Esc>
if getline('.')[col('.')-1] =~ "\\s"
" If on whitespace, go back one word
normal gE
else
" If inside word, then let's make sure we move the cursor to end of word
" because the left word could be shorter than the second word
exe "normal! viW\<Esc>"
let l:pos = getpos('.')
let l:addone = 1
endif
normal cxiWB.
call setpos('.', l:pos)
if l:beyondeol
normal $
elseif l:addone
normal l
endif
endfunction
nnoremap <silent> <M-s> :<C-u>call <SID>swap_last_two_words()<CR>
imap <silent> <M-s> <C-\><C-o>:<C-u>call <SID>swap_last_two_words_in_insert_mode()<CR>
nmap ß <M-s>
imap ß <M-s>
In the process, learned about <C-\>
from Bram and may have found a bug: vim/vim#11411