zsh right-justify in ps1
Solution 1
You will find a detailed answer and an example here. The idea is to write the line before PS1 using the precmd
callback, use $COLUMNS
, and a bit of math to calculate the position of the text on the right side of the screen. Knowledge of escape sequences will also help you with cursor positioning and colouring.
Another solution can be to use a theme from Oh My ZSH.
Solution 2
I've been looking for this too. For me, the fact that precmd()
drawn lines don't redraw on resize or when ^L
is used to clear the screen was something that kept itching at me. What I'm doing now is using ANSI escape sequences to move the cursor around a bit. Though I suspect there is a more elegant way to issue them, this is working for me:
_newline=$'\n'
_lineup=$'\e[1A'
_linedown=$'\e[1B'
PROMPT=...whatever...${_newline}...whatever...
RPROMPT=%{${_lineup}%}...whatever...%{${_linedown}%}
Keep in mind that the zsh manual states that %{...%} is for literal escape sequences that don't move the cursor. Even so, I'm using them because they allow to ignore the length of it's content (couldn't figure out how to issue the escape that moves the cursor using them though)
Solution 3
Here is how I've configured this thing just now. This approach doesn't require any escape-sequence manipulations, but will make you have two different variables for primary prompt: PS1
with coloring and NPS1
without.
# Here NPS1 stands for "naked PS1" and isn't a built-in shell variable. I've
# defined it myself for PS1-PS2 alignment to operate properly.
PS1='%S%F{red}[%l]%f%s %F{green}%n@%m%f %B%#%b '
NPS1='[%l] %n@%m # '
RPS1='%B%F{green}(%~)%f%b'
# Hook function which gets executed right before shell prints prompt.
function precmd() {
local expandedPrompt="$(print -P "$NPS1")"
local promptLength="${#expandedPrompt}"
PS2="> "
PS2="$(printf "%${promptLength}s" "$PS2")"
}
Note the usage of print -P
for prompt expansion, ${#variable}
for getting the length of string stored in variable, and printf "%Nd"
for left-hand padding with N
spaces. Both print
and printf
are built-in commands, so there should be no performance hit.
Solution 4
Let's define prompt with this layout:
top_left top_right
bottom_left bottom_right
To do this, we'll need a function that tells us how many characters a given string takes when printed.
# Usage: prompt-length TEXT [COLUMNS]
#
# If you run `print -P TEXT`, how many characters will be printed
# on the last line?
#
# Or, equivalently, if you set PROMPT=TEXT with prompt_subst
# option unset, on which column will the cursor be?
#
# The second argument specifies terminal width. Defaults to the
# real terminal width.
#
# Assumes that `%{%}` and `%G` don't lie.
#
# Examples:
#
# prompt-length '' => 0
# prompt-length 'abc' => 3
# prompt-length $'abc\nxy' => 2
# prompt-length '❎' => 2
# prompt-length $'\t' => 8
# prompt-length $'\u274E' => 2
# prompt-length '%F{red}abc' => 3
# prompt-length $'%{a\b%Gb%}' => 1
# prompt-length '%D' => 8
# prompt-length '%1(l..ab)' => 2
# prompt-length '%(!.a.)' => 1 if root, 0 if not
function prompt-length() {
emulate -L zsh
local COLUMNS=${2:-$COLUMNS}
local -i x y=$#1 m
if (( y )); then
while (( ${${(%):-$1%$y(l.1.0)}[-1]} )); do
x=y
(( y *= 2 ));
done
local xy
while (( y > x + 1 )); do
m=$(( x + (y - x) / 2 ))
typeset ${${(%):-$1%$m(l.x.y)}[-1]}=$m
done
fi
echo $x
}
We'll need another function that takes two arguments and prints a full fine with these arguments on the opposing sides of the screen.
# Usage: fill-line LEFT RIGHT
#
# Prints LEFT<spaces>RIGHT with enough spaces in the middle
# to fill a terminal line.
function fill-line() {
emulate -L zsh
local left_len=$(prompt-length $1)
local right_len=$(prompt-length $2 9999)
local pad_len=$((COLUMNS - left_len - right_len - ${ZLE_RPROMPT_INDENT:-1}))
if (( pad_len < 1 )); then
# Not enough space for the right part. Drop it.
echo -E - ${1}
else
local pad=${(pl.$pad_len.. .)} # pad_len spaces
echo -E - ${1}${pad}${2}
fi
}
Finally we can define a function that sets PROMPT
and RPROMPT
, instruct ZSH to call it before every prompt, and set appropriate prompt expansion options:
# Sets PROMPT and RPROMPT.
#
# Requires: prompt_percent and no_prompt_subst.
function set-prompt() {
emulate -L zsh
local git_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
git_branch=${${git_branch//\%/%%}/\\/\\\\\\} # escape '%' and '\'
local top_left='%F{blue}%~%f'
local top_right="%F{green}${git_branch}%f"
local bottom_left='%B%F{%(?.green.red)}%#%f%b '
local bottom_right='%F{yellow}%T%f'
PROMPT="$(fill-line "$top_left" "$top_right")"$'\n'$bottom_left
RPROMPT=$bottom_right
}
autoload -Uz add-zsh-hook
add-zsh-hook precmd set-prompt
setopt noprompt{bang,subst} prompt{cr,percent,sp}
This produces the following prompt:
~/foo/bar master
% █ 10:51
- Top left: Blue current directory.
- Top right: Green Git branch.
- Bottom left:
#
if root,%
if not; green on success, red on error. - Bottom right: Yellow current time.
You can find extra details in Multi-line prompt: The missing ingredient and complete code in this gist.
Related videos on Youtube
So8res
Updated on September 18, 2022Comments
-
So8res over 1 year
I'd like a multi-line zsh prompt with a right alined part, that will look something like this:
2.nate@host:/current/dir 16:00 ->
I know about RPROMPT in zsh, but that has a right-aligned prompt opposite your normal prompt, which is on the same line of text as your typing.
Is there a way to have a right-aligned portion to the first line of a multi-line command prompt? I'm looking for either a directive in the PS1 variable that says 'right align now' or a variable that is to PS1 what RPROMPT is to PROMPT.
Thanks!
-
calin about 9 yearsAfter playing around with this for some time, it messes up occasionally and puts the cursor or date on the wrong line. Not such a big deal though; can just press enter to correct it.
-
CaldeiraG almost 5 yearsWelcome to Super User! Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.
-
Roman Perepelitsa almost 5 years@CaldeiraG I rewrote my answer following your suggestion. FWIW, the shape of my original answer was informed by the highest-voted and accepted answer on this question.
-
CaldeiraG almost 5 yearsLooks way better! :p Enjoy your stay here