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:

insert mode
the current zle overwrite/insert mode. Do you want the prompt to be redrawn when you change mode?precmd()
which works but$KEYMAP
doesn't seem to be available. Also, as you said, this probably doesn't allow for redrawing.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.TRAPWINCH()
handler.