I'd like to make a command that works like this: :KeepCursor {cmd} will the given execute ex command string, then restore the cursor's position. A primary example is:

:KeepCursor normal! *

which would perform a keyword search without jumping to the next match. This can of course be accomplished in other ways, but it's a good demonstration of the functionality I'm looking for, which I want to work in every possible case.

This is my attempt so far. I'm using feedkeys+imtx because I'd like the command to behave exactly as though I typed it. I put the cursor restoration in a finally so it works even if the command encounters an error.

function! s:keepcursor(qargs)
  let l:view = winsaveview()
  let l:winid = win_getid()
    call feedkeys(':'.a:qargs."\<cr>", 'imtx')
    if win_getid() != l:winid
      if !win_id2win(l:winid)
      call win_gotoid(l:winid)
    call winrestview(l:view)

command! -nargs=* KeepCursor call s:keepcursor(<q-args>)

However, it does not seem to work. Using :KeepCursor normal! * prints the search string but does not highlight anything. Running :hlsearch afterwards highlights the wrong thing.

My questions are a) can it be explained why this doesn't work and b) can such a :KeepCursor command be written?

2 Answers 2


Can it be written?


command! -nargs=* -complete=command KeepCursor
      \ let [s:view, s:win] = [winsaveview(), win_getid()] |
      \ try |
      \   execute <q-args> |
      \ finally |
      \   if win_id2win(s:win) |
      \     call win_gotoid(s:win) |
      \   endif |
      \   keepjumps call winrestview(s:view) |
      \ endtry

Can it be explained why this doesn't work

There is a lot going on here. I haven't debugged anything, but my guess is feedkeys() is part of your problem. I typically avoid feedkeys() as it is often easier to debug other methods.

  • Originally I used feedkeys because hlsearch doesn't work when using execute from a function. So basically the difference is putting execute inside the command, not inside a function?
    – Mass
    Mar 23, 2019 at 0:55
  • I have tried this a few ways. Inside a function, outside a function, w/ feedkeys(), w/ :execute, w/ call execute(). From what I can tell you can use feedkeys() , :execute, execute(), it doesn't matter. For some reason it is the function that seems to be the problem here. I only kept it out of the function so that the errors would look "native", so I stumbled on the solution by accident. This might be a a bug, but I am not on the latest version of Vim. I am on Vim 8.1 patches 1-950. Mar 24, 2019 at 1:44

Can it be written?

Possibly, but it might be a lot harder than expected. Because, what do you do with commands that close the current window? Or commands, that add lines above the current cursor position?

One problem you are running into is described unter :h function-search-undo.

The last used search pattern and the redo command "."
will not be changed by the function.  This also
implies that the effect of :nohlsearch is undone
when the function returns.

The only way I know to circumvent this is to make use of using feedkeys(), but apparently, this does not work with the x flag, you have given to the feedkeys command (and I am not actually sure this is needed here). So using

call feedkeys(':'.a:qargs."\<cr>", 'imt')

and your example starts to work (well, almost). However, if you start using this, you'll notice the cursor position will be off, despite the fact, that winrestview() should have restored the cursor position. I think this happens, because the feedkeys() call will insert the command after the current command is executed, e.g. after the function s:keepcursor() returns. I dont' know a way around this (well, perhaps a workaround would be to try to make use of timers, but I consider this ugly).

Note, there is also this script that implements a :KeepView command.

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.