6

I would like to have a two-line prompt in zsh, but collapse it to a very small one just after pressing ENTER, so that it doesn't show up in the terminal scroll history. After typing two commands, the terminal should look like this while typing the third:

> echo Command 1
Command 1
> echo Command 2
Command 2
+------------ Long prompt ----------+
`> echo typing a new command here…

I tried getting something with the preexec hook and zle reset prompt, but I get the error widgets can only be called when ZLE is active:

$ autoload -U add-zsh-hook
$ hook_function() { OLD_PROMPT="$PROMPT"; export PROMPT="> "; zle reset-prompt; export PROMPT="$OLD_PROMPT"; }
$ PROMPT=$'+------------ Long prompt ----------+\n\`> '
+------------ Long prompt ----------+
`> add-zsh-hook preexec hook_function
+------------ Long prompt ----------+
`> echo Test
hook_function:zle: widgets can only be called when ZLE is active
Test
+------------ Long prompt ----------+
`> 

2 Answers2

12

When the preexec function is called, zle is already finished and hence, zle widgets can't be used any more.

So, you have to intercept the pressing of the ENTER key before zle terminates. By default ENTER is bound to accept-line, but this might depend on other tricks you already use;

$ bindkey | grep '\^M'
"^M" accept-line

We now write a new widget we want to bind to ENTER instead:

del-prompt-accept-line() {
    OLD_PROMPT="$PROMPT"
    PROMPT="> "
    zle reset-prompt
    PROMPT="$OLD_PROMPT"
    zle accept-line
}

The logic is taken from your approach. In the last line we call the accept-line widget or anything else which was executed on pressing ENTER.

Finally we introduce the new widget to zle and bind it to ENTER:

zle -N del-prompt-accept-line
bindkey "^M" del-prompt-accept-line

Et voilà:

> echo foo bar
foo bar
+------------ Long prompt ----------+
`> echo this is my new command... not pressed ENTER, yet!
mpy
  • 28,816
1

Extending on @mpy's answer, I found that I can just pass the variable overrides directly in to the call to zle so no need to save old values. I also experimented with adding colour to the scrollback version of the prompt to clearly distinguish it from command output and to delimit the output from different commands.

A caveat is that I couldn't reset the terminal colour after redrawing the prompt, so I ended up resetting it in preexec instead, and for reasons even less clear I had to include %k in PROMPT to stop colour bleed during autocompletion. It's not ideal but it works better than trying to edit the command with alternate background colours directly.

SCROLLBACK_PROMPT='%K{blue}%B> %b'
SCROLLBACK_PS2="$PS2" # or whatever...
SCROLLBACK_PS3="$PS3"
SCROLLBACK_PS4="$PS4"

function del-prompt-accept-line() { PROMPT="$SCROLLBACK_PROMPT"
PS2="$SCROLLBACK_PS2"
PS3="$SCROLLBACK_PS3"
PS4="$SCROLLBACK_PS4"
zle reset-prompt zle .accept-line } function preexec() { print -n '\E[0m\E[K' } zle -N accept-line del-prompt-accept-line

sh1
  • 143