7

On a Linux platform I see this difference of behavior between running a shell command with :!{cmd} (which just runs the command directly) and with :w !{cmd} (which passes the buffer as standard input to the command.)

Running :!{cmd} will switch back from the alternate screen back to the main screen, which means the Vim buffer and UI are hidden and the screen on the shell where I ran Vim are presented back, then the command output is printed and the "hit return" prompt is issued.

If I start vim, then run :!echo hello, the result I see is:

$ vim

hello

Press ENTER or type command to continue

That text is also present after I exit Vim, since it's been output into the main screen, not the alternate screen that Vim is managing.

On the other hand, if I run the same command with :w !{cmd}, Vim simply prints the output of the command below the Ex command-line. For :w !echo hello, what I end up seeing is:

~
~
~
[No Name]                             0,0-1          All
:w !echo hello
hello

Press ENTER or type command to continue

You'll recognize the ~s as Vim's empty lines at the end of the buffer, and the [No Name] line as Vim's status line. The Ex command is also not cleared.

Why the difference?

The documentation isn't very clear about it. The closest thing mentioned in :help :! is that "Vim redraws the screen after the command is finished, because it may have printed any text", but that doesn't really explain why it needs to switch back to the main screen. It also mentions using :silent to prevent redrawing, but that prevents any output altogether.

Documentation in :help :write_c doesn't help much either, it says *"{cmd} is executed like with :!{cmd}", but it's actually referring to how !s are special in the command itself...

Is there a way to make :!{cmd} behave like :w !{cmd}, without switching back from the alternate screen?

Or is there another way to run an external command on the alternate screen, that doesn't involve passing it lines from the current buffer as standard input?

10
  • 2
    is there another way to run an external command on the alternate screen, that doesn't involve passing it lines from the current buffer as standard input? try to remove ^[[?1049h from &t_ti and ^[[?1049l from &t_te (0x0.st/iQDw.txt ). Look at xterm's documentation, in particular the sequence CSI ? Pm h, and the line starting with Ps = 1 0 4 9.
    – user938271
    Commented Apr 17, 2020 at 13:06
  • 1
    (same behavior on macOS + Alacritty/Terminal.app)
    – D. Ben Knoble
    Commented Apr 17, 2020 at 15:31
  • 1
    @user938271 I also found a plug-in using set t_ti= explicitly, I looked for it again and found it was vim-flake8. It's on a slightly different context (using 'grepprg') but still related. Not sure if this is a more widespread practice or not... Do you know of other examples (or have you written any plugins yourself) which use this same technique?
    – filbranden
    Commented Apr 22, 2020 at 18:32
  • 2
    I know about vim-altscreen; other than that, no I don't know about anything else. I remember tweaking the options a long time ago, but I reverted the change; I can't remember why.
    – user938271
    Commented Apr 22, 2020 at 19:45
  • 1
    I don't think there's anything worth except patching source code. (BTW. Tried if (addr_count == 0) do_in = do_out = FALSE; and then do_filter(...); apply_autocmds(...); instead of that whole "if" block - everything looks fine).
    – Matt
    Commented Apr 25, 2020 at 5:29

1 Answer 1

2
+200

is there another way to run an external command on the alternate screen, that doesn't involve passing it lines from the current buffer as standard input?

Try to remove ^[[?1049h from 't_ti' and ^[[?1049l from 't_te':

augroup NoAltScreen
    au!
    au VimEnter * call s:altscreen(v:true)
    au VimLeave * call s:altscreen(v:false)
augroup END

fu s:altscreen(disable) abort
    if a:disable
        let [s:t_ti_save, s:t_te_save] = [&t_ti, &t_te]
        let &t_ti = substitute(&t_ti, '\e\[?1049h', '', '')
        let &t_te = substitute(&t_te, '\e\[?1049l', '', '')
    else
        if exists('s:t_te_save')
            let &t_te = s:t_te_save
            let &t_ti = s:t_ti_save
        endif
    endif
endfu

Look at xterm's documentation, in particular the sequence CSI ? Pm h, and the line starting with Ps = 1 0 4 9:

CSI ? Pm h

...

Ps = 1 0 4 9 ⇒ Save cursor as in DECSC, xterm. After sav- ing the cursor, switch to the Alternate Screen Buffer, clear- ing it first. This may be disabled by the titeInhibit resource. This control combines the effects of the 1 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based applications rather than the 4 7 mode.


This is what the plugin vim-altscreen does to prevent the output of a shell command from being displayed in the original screen buffer from which you started Vim.


Don't make the value empty, it may contain some sequences which you might want to keep.

As an example, I use 't_ti' and 't_te' to set the cursor shape, and in the past to restore the focus events in the terminal.

1
  • Thanks for making it into an answer! Yeah great point about not making the value empty, since like you mentioned, it can be used for other purposes not directly related to switching to the altscreen.
    – filbranden
    Commented Apr 28, 2020 at 15:44

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.