4

I have the following command in vim to scroll another window when only two windows are open. The specifics are not important as this is a general question, but the example is illustrative.

nnoremap <leader>d <C-w>w<C-d><C-w>w

This works fine most of the time, but if the other window is already at the bottom then I find my cursor often ends up stuck in the other window. I'm guessing this is because the <C-d> fails and the command stops running.

I've come across the same problem in other remappings and I'm sure there is a simple way to code around it. I know that for example in search and replace you stick an e at the end. For example:

bufdo s/foo/bar/ge | update

This allows later commands to run even if no patterns match.

Any help would be greatly appreciated.

3

If you have 2 windows, in both of them you are in the middle of a buffer, and you type the following Ex command:

:exe "norm! \<C-W>w\<C-D>\<C-W>w"

It should do what your mapping is trying to perform, which is scrolling the other window downwards and give the focus back to the original window.

But if in the other window, your cursor is already at the very bottom of the buffer, it will not give the focus back because, like you said, the <C-D> will fail and therefore the last keystrokes, <C-W>w, won't be hit by :normal.

A possible solution is to break the :normal command into 2 parts, where an error may occur.
Here, the error may occur on <C-D>, so you could type the following Ex command:

:exe "norm! \<C-W>w\<C-D>" | exe "norm! \<C-W>w"

Now, even if the first :normal command fails at the end, the second :normal will still be executed regardless.


To convert the previous command into a mapping, you could write this in your vimrc:

nnoremap <silent> <leader>d :<C-U>exe "norm! \<lt>C-W>w\<lt>C-D>" <bar> exe "norm! \<lt>C-W>w"<CR>

A few things that may help you better understand the previous mapping:

  • In the {rhs} of the mapping, <C-U> is there just in case you hit a number (by accident) before <leader>d.
    If you hit a number before an Ex command, it's automatically converted into a range on the command line. For example, if you hit 5:, you should see :.,.+4.
    But :execute doesn't accept a range, so it would raise an error.
    With <C-U>, if a range is inserted by accident, it will be deleted.

  • :normal doesn't need :execute to type normal commands, however, :execute allows you to prevent :normal from typing the whole command line including the pipe.
    That's because :normal interprets | as a part of its argument, while :execute interprets it as a command termination.
    Besides, it allows you to wrap the whole :normal command inside a string where control characters may be represented with printable characters (a special type of notation described in :help expr-quote). For example, here it allows you to represent a literal <C-D> with the notation \<C-D>.

  • The bang after the :normal command is here to prevent the keystrokes that you pass as an argument to be remapped. From :help :normal:

    Without it, when this command is called from a
    non-remappable mapping (:noremap), the argument can
    be mapped anyway.
    

    Usually you don't want the keys to be remapped, so even if it should not be necessary here, it can be a good habit to put a bang after :norm. But if for some reason you wanted the keys to be remapped, you could remove the bang.

  • When the string executed by :execute contains special keys such as <C-D> or <C-W>, you should use double quotes instead of single ones, otherwise they won't be translated into literal control characters. As a result, instead of typing a literal <C-D>, :normal would type 5 keys <, C, -, D and >.

  • In a mapping, the notation <bar> can be used to represent a pipe character. Another possibility is \|, but not |. A simple pipe would be interpreted by :nnoremap as a command termination instead of a usual character to type.

  • The keycode <lt> is necessary for the mapping to work. Without it, Vim will automatically translate <C-W> and <C-D> into literal control characters when your vimrc is sourced and the mappings table is filled with all your mapping commands (the one you see through Vim's internal pager when you type :map).


To better understand why <lt> is necessary, suppose you want to define a mapping in command line mode which maps cw to the five characters <C-W>, and you write this in your vimrc:

cnoremap cw <C-W>

Now, on the command line, you type (without hitting Enter at the end):

:exe "norm! \

Then, you hit cw thinking that it will insert <C-W> on the command line.
But instead of getting :exe "norm! \<C-W>, you will get :exe "norm!. Why?

Because, when your :cnoremap command was sourced, inside the mappings table, <C-W> was automatically translated into a literal control character.

What's the effect of a literal <C-W> on the command line? It deletes the previous word.
Here the previous word was the backslash, so it was deleted. But that's not what you wanted.
You wanted the five characters <C-W> to be typed without any translation.
So a better mapping would be:

cnoremap cw <lt>C-W>

This time, it will work as expected, because when the command will be sourced, inside the mappings table, only <lt> will be translated (into the character <) and the result will be the 5 desired characters < + C-W> = <C-W>.

You can see <lt> as a way to protect a special key from being automatically translated when it's inside the {rhs} of a mapping.

  • Fantastic answer, extremely thorough and I understand now. Thank you. – campbellC Mar 12 '16 at 15:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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