I wrote a quick snippet of VimL to display, in my statusline, the type of the currently focused Haskell expression It looks like this (click for better quality (as gfy)):

Screen-capture of a cursor moving around a vim buffer, with the statusline constantly updating to show the type of the selected expression

I've included the code below, but the only important part is the system call. The call takes about 0.04 s to execute, which is fine with me; there's really no noticeable delay.

However, what does bother me greatly is that if I send a key event while the system call is running, the key is echoed to the display and overwrites a character. Of course, it doesn't actually modify the buffer, and hitting <C-L> is sufficient to fix it, but this happens whenever I'm pressing keys rapidly in sequence or holding down a key, so writing <C-L> all the time is very frustrating.

(Worse, it means that I can't trust my own eyes!)

How can I solve this problem?

To reproduce this, create a file with ten or so lines (like 10Ohello<Esc>) and save it as sample.txt. Save the below text block as sample.vimrc. Then run vim sample.txt -u sample.vimrc and hold down j; you should see a bunch of js appear. Hit <C-L> to clear them.

Here's sample.vimrc:

set nocompatible

set statusline=%{SlowOutput()}
function! SlowOutput()
    silent call system("sleep 0.1")
    return "I'm awake; I'm awake!"

set laststatus=2

" tried with and without this (I would *very* much prefer to keep it on):
" set lazyredraw

The actual code I'm using, in case I'm missing something important or you just want the code for yourself, follows (it depends on bling/vim-airline and bitc/vim-hdevtools):

let g:airline_section_gutter = "%{HaskellCursorType()}%="
function! HaskellCursorType()
    if &l:ft != "haskell"
        return ""
        let l:file = expand("%")
        if l:file == ''
            return ""
        if !filereadable(l:file)
            return ""
        let l:line = line('.')
        let l:col = col('.')
        let l:cmd = hdevtools#build_command('type', shellescape(l:file) . ' ' . l:line . ' ' . l:col)
        silent let l:output = system(l:cmd)
        let l:lines = split(l:output, '\n')
        if len(l:lines) == 0
            return "(no type)"
        return matchstr(l:lines[0], '"\zs[^"]\+\ze"')
  • (Sorry if the tags are way off—feel free to edit.) – wchargin Jan 24 '16 at 17:04
  • 1
    What happens if you clear the screen in vimscript, right at the end of the statusline update (after the system call)? Also, do you have a recent Vim version - newer versions may have that graphical glitch solved. Also, ideally you would never run lengthy operations to update the statusline; some other operation should update a variable, which the statusline should read when it needs to update itself. – VanLaser Jan 25 '16 at 1:29
  • 1
    I don't have this problem in xterm with Vim 7.4.712. Which Vim version and terminal emulator are you using? – Martin Tournoij Jan 25 '16 at 2:11
  • 1
    I also do not have this problem (Vim 7.4.1089) (neither in vim in urxvt or gvim). – Karl Yngve Lervåg Jan 25 '16 at 9:11
  • 2
    Indeed, and I suggest to use CursorHold / CursorHoldI events to update the variable, if possible. IIRC this is the method used by tagbar.vim (which also has an vim-airline extension). – VanLaser Jan 25 '16 at 16:00

I have seen this problem in other plugins as well (and some take a LOT more than 0.04 seconds).

The issue appears to be that Vim puts the terminal into "cooked" mode so that the command that is run can receive interrupt signals (from ctrl+c). This means that, for the duration of the command, your terminal is in line-editing mode. You can try this yourself by running, say, "sleep 5" in a system command and observe that wherever your cursor was when it was run, what you type starts there, and you can use backspace, ctrl+u, etc to edit the line. Whatever the line was when the command exits after your editing is then read by vim and processed.

The only Vim Proper ways to spawn an external program I can find (:!cmd, :r!cmd, and system("cmd")) all suffer this, though only system doesn't redraw the screen, but all will flicker with redrawing.

Neovim is doing some work for handling terminal programs, but that doesn't help if your target audience includes Vim (and I haven't kept up with it so I don't know where that stands)

Due to the architecture, I don't think there is a way around this in Vim proper. No matter how fast your external program runs, there is always a period of time where a character can enter the terminal while in cooked mode.

If you are willing to depend on one of the language extensions of Vim, you could use that to run your external command without changing the terminal mode.

For example:

:python import os
:python os.system("sleep 5")

with the result that the terminal freezes for 5 seconds while the command runs. The text you type will make it to Vim afterwards without any line editing by the terminal (e.g. backspaces , ctrl+u, etc. will be interpreted by vim and in order of characters typed).

The downsides of course are:

  • adds an extra dependency to your plugin (python, perl, or whatever you use)
  • There is no way from the terminal to interrupt the operation now (can have a hung terminal unless someone interrupts the program via other means, like a kill command in another shell or process manager. Any ctrl+c a user types will be interpreted by vim once the command ends)

The latter might be mitigated by adjusting the terminal settings yourself in, say, python prior to calling the external command. Be careful there, though.

  • Thank you. This is interesting. I will take another look at neovim. Unfortunately, the command sometimes (rarely) does hang when I've configured things wrong, so the python trick isn't an option, but it's very good to know about the difference between cooked mode and raw mode. – wchargin Feb 27 '16 at 21:12

Your Answer

