I'm trying to write a vimscript function that moves the cursor all the way up or all the way down without changing the column. (similar to the way :set nostartofline works).

It works, but when I tried to add a mapping for visual mode, it would exit visual mode.

Here is my script:

function! StraightDown()
  let line=line('.')
  let end=line('$')
  exe "normal ".eval(end-line)."j"

function! StraightUp()
  let line=line('.')
  exe "normal ".eval(line-1)."k"

nnoremap <M-j> :<C-u>call StraightDown()<cr>
nnoremap <M-k> :<C-u>call StraightUp()<cr>

xnoremap <M-j> :<C-u>call StraightDown()<cr>
xnoremap <M-k> :<C-u>call StraightUp()<cr>

After experimenting with it, it seems like any command executed with normal ... will leave visual mode unless it starts with v.

One possible workaround I thought of is to add some more logic, and if I call the function in visual mode, add v to the normal command. However, this doesn't work if I'm in visual line or visual block mode.

So really, my TL;DR question is how can I do a normal command in visual mode without changing modes?

  • Why don't you simply set nostartofline and use gg and G?
    – statox
    Commented Jun 20, 2016 at 7:32
  • @statox Well, 1. is because I like to think of them as two separate commands, and 2. I'm trying to get better at vimscript. I guess it's practice? I'm fairly new to vimscript.
    – DJMcMayhem
    Commented Jun 20, 2016 at 7:35
  • Yes set nostartofline and gg are two separate commands but I don't understand how it is a problem: simply put set nostartofline in your vimrc and use gg when you need it. Now if you'd like to keep startofline for other commands and nostartofline only for gg you could create a mapping like that nnoremap gg :set nostartofline<CR>:normal! gg<CR>:set startofline<CR>. Now I understand you want to get better at vimscript, I'm just suggesting a workaround for your original problem :-)
    – statox
    Commented Jun 20, 2016 at 7:44

1 Answer 1


There are two things we need to do:

  1. Since you are using a mapping to execute your function, the mapping rightly has to do :<c-u> in order to reliably call the function. But calling a function that way will indeed break us out of visual mode. So you should restore the last visual selection by putting normal gv near the top of your function.

    But since you only want to do that in visual mode, let's do it like this:

    function! StraightDown()
    function! StraightDownVisual()
        normal gv
        call StraightDown()
    nnoremap <M-j> :<C-u>call StraightDown()<cr>
    vnoremap <M-j> :<C-u>call StraightDownVisual()<cr>

    (An alternative single-function approach would be to pass an argument to indicate whether we were in visual mode or not: call StraightDown(0) or call StraightDown(1). Or another approach is here.)

  2. I suspect your exe "normal ..." will now work fine, preserving the visual selection. But if it really does clear it, then don't use normal to move the cursor, call a Vim function instead:

    let target_col = getcurpos()[4]
    call setpos('.', [0, end, target_col])

    For more info, see:

    :h setpos(
    :h getcurpos(

    You could use [2] of getcurpos() for the current column, but if [4] works for you, then that's probably what you really wanted.

    Interestingly, if I press $ and then ask

    :echo getcurpos()

    then the last number displayed is 2147483647. I think that is Vim's way of telling me the user wants to be on the final column, not whatever column they have actually landed on.

  • Thanks for your help. Unfortunately, this still clears visual mode.
    – DJMcMayhem
    Commented Jun 20, 2016 at 18:12
  • 1
    @DJMcMayhem Ah. Since you are executing your function through a mapping then the visual selection is always cleared, regardless whether you use normal or not in your function. So ... your functions should first do normal gv to restore the visual selection, and then call setpos(...). I have updated my answer, HTH! Commented Jun 21, 2016 at 3:56

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.