5

I often find myself accidentally pressing <c-e>/^E while in normal mode inside a terminal buffer. This causes the terminal buffer to scroll down and a line with a ~ is displayed, like when your file doesn't reach the bottom of the screen.

I've created a mapping that stops ^E from being called if the current buffer is a terminal.

nnoremap ^E :call ScrollStop()<cr>
function! ScrollStop()
  if &buftype !=# "terminal"
    normal! ^E
  endif
endfunction

I would now like to do the same thing for ^Y by extending the function to allow me to pass in either ^E or ^Y but all my attempts so far have caused various errors, mainly E119: Not enough arguments for function: ScrollStop.


Thanks to the comments by @GaryJohn we've narrowed the problem down. If we have the following function:

function! MyScroll(key)
   exe 'normal' a:key
endfunction

and we call it via :call MyScroll('^E') where ^E is entered via ctrl-v + crtl-e then we get the expected result, the buffer scrolls down one line.

If however we create a mapping :nnoremap ^E :call MyScroll('^E')<cr> then press ^E, we get an error E471: Argument required: normal. This happens for both Vim and Neovim.

  • What editor are you using? There is no mention of a "terminal" buftype in the Vim reference manual. – garyjohn Dec 8 '15 at 4:28
  • 3
    I don't consider this an actual answer to your question, but since you'll be defining maps for both ^E and ^Y, why could you just not call the function with :call ScrollStop('E') and :call ScrollStop('Y') respectively? Why would it need to be the control character and not just a string? – Sakari Cajanus Dec 8 '15 at 7:59
  • @garyjohn, I'm using Neovim but the question isn't specific to terminal buffers. – Brett Y Dec 8 '15 at 18:49
  • @SakariCajanus certainly I can do that and then just have an if statement in my function but I'd like to know if there is a way to just pass the control-character so I don't need to add the if statements, which saves a small amount of code in my trivial example but could be useful in other cases – Brett Y Dec 8 '15 at 18:53
  • 3
    It's true that the answer isn't specific to terminal buffers, but it is specific to the editor, and it could depend on differences between Neovim and Vim. The following works with Vim and may work with Neovim as well. :function MyScroll(key) :exe 'normal' a:key :endfunction :call MyScroll('^E') where I entered ^E by typing Ctrl-V Ctrl-E. – garyjohn Dec 8 '15 at 21:31
3
+50

This is what you want to use:

nmap <expr> <c-e> &buftype !=# 'terminal' ? "\<c-e>" : ''

To make this more convenient:

function! s:terminal_ignore(key, ...) abort
  execute printf('nmap <expr> %s &buftype !=# "terminal" ? "%s" : "%s"',
        \ a:key, a:key, a:0 ? a:1 : '')
endfunction

call s:terminal_ignore("\<c-e>")
call s:terminal_ignore("\<c-y>")

This uses :h map-<expr> to check &buftype. If it's terminal, return an empty string, which is noop.

A few things to note:

  • This is compatible with Vim.
  • nmap is used instead of nnoremap. It doesn't matter in the above script since it's returning the same key for non-terminal buffers. However, if you decide that the expression should return something else for the terminal, you will want it to be recursive.
  • The returned string must be a constant string (:h expr-quote). "\<c-e>" is CTRL-E/^E, '<c-e>' is literally <c-e>. In the s:terminal_ignore() function, the constant string "\<c-e>" is passed so that it can be used in {lhs} and {rhs}. Only {lhs} will evaluate <c-e> as CTRL-E, but it works fine with a constant string. {rhs} is used literally.
  • You can pass an optional second argument that will be used if the current buffer is a terminal. e.g. call s:terminal_ignore("\<c-e>", "A<c-e>") to put the terminal back into insert mode and type CTRL-E.
  • I'm using <c-e> instead of the literal ^E because I want my scripts to be legible in non-vim programs.
2

The following function and calls to that function work in Vim. It seems like they should work in Neovim but I don't have a Neovim to test.

function MyScroll(key)
    exe 'normal' a:key
endfunction

call MyScroll('^E')
call MyScroll('^Y')

where the ^E and ^Y in the calls to MyScroll() above are literal Ctrl-E and Ctrl-Y characters entered by typing the two-character sequences Ctrl-V Ctrl-E and Ctrl-V Ctrl-Y, respectively.

1

You want normal! so you don't call your own mapping recursively:

:norm[al][!] {commands}                                 :norm :normal
                        Execute Normal mode commands {commands}.  This makes
                        it possible to execute Normal mode commands typed on
                        the command-line.  {commands} are executed like they
                        are typed.  For undo all commands are undone together.
                        Execution stops when an error is encountered.

                        If the [!] is given, mappings will not be used.
                        Without it, when this command is called from a
                        non-remappable mapping (:noremap), the argument can
                        be mapped anyway.

So your function should be:

function! MyScroll(key)
   exe 'normal!' a:key
endfunction
1

This is a slight variation of garyjohn's answer which removes the requirement to enter control characters directly into your Vimscript by adding the Ctrl modifier inside the function.

function! ScrollStop(key)
  if &buftype !=# "terminal"
    execute 'normal! ' . nr2char(and(char2nr(a:key), "0b0011111"))
  endif
endfunction

nnoremap <silent> <C-E> :call ScrollStop('E')<cr>
nnoremap <silent> <C-Y> :call ScrollStop('Y')<cr>

It takes advantage of the way the Ctrl key was implemented historically in terminals: by clearing the 6th and 7th bits of the ASCII character: we do this by and-ing with the binary value 0011111

(I could also have used the expressionnr2char(toupper(char2nr(a:key)) - 64). Ctrl-modified keystrokes are always 64 spots below the upper-case version of the unmodified keystroke: converting to upper-case is the same as clearing the 6th bit; subtracting 64 is the same as clearing the 7th.)

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.