5

I'd like to have the left and right prompt in one line and a background color spanning the full line. The user input should be in the next line.

hostname | ~/path/to/cwd                              branch master | insert mode
$ _

For the linebreak I tried to use $'\n' in PS1 but that causes the right prompt to be drawn in the second line as well.

For coloring the whole line, I tried to have %K{green} in PS1 without closing it, but the background coloring stops right after the last character of the left prompt.

How can I realize this prompt?

8
  • Is that insert mode the current zle overwrite/insert mode. Do you want the prompt to be redrawn when you change mode? Dec 19, 2015 at 21:26
  • @StéphaneChazelas Yep. That seems to be the main challenge. I found other answers about drawing the line in precmd() which works but $KEYMAP doesn't seem to be available. Also, as you said, this probably doesn't allow for redrawing.
    – danijar
    Dec 19, 2015 at 21:32
  • @StéphaneChazelas Okay. By the way, I guess there is no chance to make the prompts reflow when resizing the console, right?
    – danijar
    Dec 19, 2015 at 23:29
  • zsh redraws its prompt upon SIGWINCH, so if you take my approach (use precmd only to generate the data to be put in the prompt, but does the display handling and calculation using defered (at the time of prompt evaluation) variable substitution) it should work. The code in my answer works OK upon window resize. Dec 19, 2015 at 23:40
  • You can also add your TRAPWINCH() handler. Dec 19, 2015 at 23:45

1 Answer 1

6

You could do something like:

branch=master mode=insert
setopt promptsubst
left='%m | %~'
PS1='%K{green}$left${(l,COLUMNS-${#${(%)left}},)${${:-$branch | $mode}//[%]/%%}}%k$ '

We use the ${(l,length,)...} left-pad operator to pad the right hand side of the prompt with $COLUMNS minus the length of what was displayed on the left hand side.

For $mode to be updated when you press Insert, you'd do:

update-mode() {
  case $KEYMAP in
    (main)
      case $ZLE_STATE in
        (*insert*) mode=insert;;
        (*) mode=overwrite
      esac;;
    (*) mode=$KEYMAP
  esac
  [[ $mode = $oldmode ]] || zle reset-prompt
}

overwrite-mode() {
   zle ".$WIDGET"
   update-mode
}
zle -N overwrite-mode

That causes the prompt to be redrawn when you switch mode.

For vi mode, you'd also need to consider all the cases where the insert mode is entered (in insert mode (a/A, i/I, c/C, s/S) or overwrite (R)), which you can do with the zle-keymap-select special hook widget (as you found out):

zle -N zle-keymap-select update-mode

I'd rewrite your final solution as:

bindkey -v

# Bind the <Insert> key.
bindkey $terminfo[kich1] overwrite-mode
setopt promptsubst

(){ # local scope

  local left right invisible leftcontent

  # User name.
  left+='%B%F{black}%K{green} %n '
  # Current working directory.
  left+='%K{yellow} %~ '

  # Version control branch.
  right='${vcs_info_msg_0_:+${vcs_info_msg_0_//[%]/%%} }'
  # Virtualenv.
  export VIRTUAL_ENV_DISABLE_PROMP=1
  right+='${VIRTUAL_ENV:+venv }'

  # Editing mode. $ZLE_MODE shouldn't contain %, no need to escape
  ZLE_MODE=insert
  right+='%K{green} $ZLE_MODE'

  # closing
  right+=$' %k%f%b'

  # Combine left and right prompt with spacing in between.
  invisible='%([BSUbfksu]|([FBK]|){*})'

  leftcontent=${(S)left//$~invisible}
  rightcontent=${(S)right//$~invisible}

  PS1="$left\${(l,COLUMNS-\${#\${(%):-$leftcontent$rightcontent}},)}$right%{"$'\n%}$ '
}

autoload vcs_info
precmd() vcs_info

update-mode() {
  case $KEYMAP in
    (main)
      case $ZLE_STATE in
        (*insert*) ZLE_MODE=insert;;
        (*) ZLE_MODE=overwrite
      esac;;
    (*) ZLE_MODE=$KEYMAP
  esac
  [[ $ZLE_MODE = $oldmode ]] || zle reset-prompt
}

overwrite-mode() {
   zle ".$WIDGET"
   update-mode
}
zle -N overwrite-mode
zle -N zle-keymap-select update-mode

That would minimise the number of unnecessary forks and prompt redraws, and allow the shell to draw the prompt correctly in all circumstances including when the windows is being resized.

Screenshot:

enter image description here

4
  • Thanks a lot. Please see my update. Just the mode switching doesn't work yet. The prompt just doesn't change. I use vi mode btw.
    – danijar
    Dec 19, 2015 at 22:11
  • Sorry, I had forgotten to include the zle -N call. Note that $ZLE_STATE would still include insert/overwrite in vi mode, but in vi mode, you'd also need to wrap i, I, a, A, R, s, S, c... or possibly find another way to hook yourself everytime the insert mode is entered. Dec 19, 2015 at 22:25
  • I found Adding Vi To Your Zsh for the mode switching. Only problem is that it erases one line above the prompt every time I switch the modes. I updated the code in the question.
    – danijar
    Dec 19, 2015 at 22:26
  • @danijar, just remove the \n (that \n is not needed because we're at the end of the line and it messes up reset-prompt calculations) Dec 19, 2015 at 22:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .