0

In VSCode I had a keybinding I really liked. Basically you could use Alt+Shift+Up/Down arrow to copy the selected block of code above or below the active selection. Now that I switched to Vim I thought certainly it's possible to remap it so it behaves exactly the same. The behaviour in VSCode is the following:

  1. Look at all the lines that are touched by the selection. Let's call this the block.
  2. Copy the block above/below the block, selection and cursor position will also go to the newly inserted block.

I want to map the behviour to Shift+Alt+j or Shift+Alt+k. I figured I could use the :t command, and this is how far I got:

" Copy the current line up
nnoremap <ESC>K :t .-1<CR>==
vnoremap <ESC>K :t '<-1<CR>gv

" Copy the current lines down
nnoremap <ESC>J :t .<CR>==
vnoremap <ESC>J :t '>+1<CR>gv

However this solution has some problems. First of all, the cursor position is always reset. Then, the selection stays on the old block instead of jumping to the new one. Also there's this weird behaviour of undo (u) that if executed after copying a block of lines up/down in visual mode it will first convert to lowercase, then uppercase, and only then remove the block (undo the copy), so I have to press u three times. This really seems a little buggy.

How to do these keybindings correctly?

2 Answers 2

1

Your normal mode mappings seem to work correctly.

Your visual mode mappings have a few problems, though.

Selection

:help gv visually selects the last selection. That is not at all what you want to happen, so gv is definitely not the right tool for the job.

What you want to do is "select the new text". This is done with :help V and the marks :help '[ and :help '], which delimit the last "change". In this case:

  • the last change is the lines that you copied above the selection,
  • the cursor is left on mark '] after the copy,
  • you only have to visually select from the cursor to mark '[.

Here is how your visual mode mappings should look:

xnoremap <Esc>K :t '<-1<CR>V'[
xnoremap <Esc>J :t '>+1<CR>V'[

Addressing

:help :t copies the given lines below the given address. If you do :t '>+1, the current line will appear two lines below mark '>, which is not what you want.

Another problem with that +1 is that it will throw an error if the visually selected lines are at the end of the buffer because that address doesn't exist.

Therefore:

xnoremap <Esc>K :t '<-1<CR>V'[
xnoremap <ESC>J :t '><CR>V'[

Cursor position

When the area covered by visual mode has been defined with a motion, as is the case with gv or V'[, the cursor is always on one end of the selection and there is no way to change that.

Undo

In visual mode, u makes the visually selected text lowercase. A second u, now in normal mode, undoes the lowercasing, and a third u finally undoes the copy. This is directly caused by the fact that you are still in visual mode when you do u. There is no "undo" feature in visual mode because it wouldn't make sense in that context: there is never any change to undo. Just leave visual mode before pressing u.

Mapping u in visual mode to "undo", as suggested in the other answer, is not a particularly bright idea. You would gain a pointless feature and loose a useful one.

3
  • These mappings work almost perfectly. It's really a pitty that the selection restore won't work. I thought there was a trick in setting a mark and then navigating to it, but I guess that doesn't work here?
    – glades
    Commented Jul 24 at 17:36
  • The core issue is that the "original" cursor position is lost very early, when you enter visual mode. So there is nothing to refer to by the time your mappings are executed. One could imagine a setup where you save the position when entering visual mode and do some extrapolation to put it at the desired place after the lines are copied but the event ModeChanged is triggered too late for that. Mapping V to something that leaves a mark before V might be a hack-ish starting point but placing the cursor in the new text will require some possibly non-trivial computation anyway.
    – romainl
    Commented Jul 25 at 5:05
  • Ok thanks for explaining I figure these work just well (maybe it's even a feature the cursor is reset, I will have to see for myself :) )
    – glades
    Commented Jul 25 at 15:28
0

I would do:

vnoremap <Esc>J yP1v
vnoremap <Esc>K ygvo<Esc>p1v

Where:

  • 1v select a range of the shape than the previous selection but starting at the cursor position.
  • gvo<Esc> move the cursor at the end of the previous selection.

These are solving your selection problem.

Remark: for the undo problem I suspect that your problem is that you hit u in visual mode. The first hit set the selection in lowercase and quit the visual mode, the second undo the lowercase action and the third do what you want.

Solution 1: hit Esc to leave visual mode first.

Solution 2: remap u to <Esc>u and lose the switch to lowercase action

vnoremap u <Esc>u
1
  • Why would you do that? How are these mappings better than OP's?
    – romainl
    Commented Jul 24 at 7:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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