Skip to content

Instantly share code, notes, and snippets.

@romkatv
Last active February 17, 2024 11:00
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save romkatv/2a107ef9314f0d5f76563725b42f7cab to your computer and use it in GitHub Desktop.
Save romkatv/2a107ef9314f0d5f76563725b42f7cab to your computer and use it in GitHub Desktop.
Two-line ZSH prompt
# Example of two-line ZSH prompt with four components.
#
# top-left top-right
# bottom-left bottom-right
#
# Components can be customized by editing set-prompt function.
#
# Installation:
#
# (cd && curl -fsSLO https://gist.githubusercontent.com/romkatv/2a107ef9314f0d5f76563725b42f7cab/raw/two-line-prompt.zsh)
# echo 'source ~/two-line-prompt.zsh' >>~/.zshrc
#
# Accompanying article:
# https://www.reddit.com/r/zsh/comments/cgbm24/multiline_prompt_the_missing_ingredient/
#
# This is only an example. If you are looking for a good ZSH prompt,
# try https://github.com/romkatv/powerlevel10k/.
# 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.
#
# The result is stored in REPLY.
#
# 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 -i 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
while (( y > x + 1 )); do
(( m = x + (y - x) / 2 ))
(( ${${(%):-$1%$m(l.x.y)}[-1]} = m ))
done
fi
typeset -g REPLY=$x
}
# Usage: fill-line LEFT RIGHT
#
# Sets REPLY to LEFT<spaces>RIGHT with enough spaces in
# the middle to fill a terminal line.
function fill-line() {
emulate -L zsh
prompt-length $1
local -i left_len=REPLY
prompt-length $2 9999
local -i right_len=REPLY
local -i 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.
typeset -g REPLY=$1
else
local pad=${(pl.$pad_len.. .)} # pad_len spaces
typeset -g REPLY=${1}${pad}${2}
fi
}
# 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 '%'
# ~/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.
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'
local REPLY
fill-line "$top_left" "$top_right"
PROMPT=$REPLY$'\n'$bottom_left
RPROMPT=$bottom_right
}
setopt no_prompt_{bang,subst} prompt_{cr,percent,sp}
autoload -Uz add-zsh-hook
add-zsh-hook precmd set-prompt
@Moarram
Copy link

Moarram commented Feb 1, 2022

First of all, thank you for sharing the prompt-length function. It really is the "missing ingredient" as you say, and it inspired me to make a prompt of my own.

However, I've found that when the measured string happens to be the exact width as the terminal, it computes a length of 0. In fact, it seems that the calculated length is modulo the number of columns. Is it possible to find the length of the printed string independently of the $COLUMNS variable?

@romkatv
Copy link
Author

romkatv commented Feb 1, 2022

@Moarram Pass 1000000 as the second (optional) argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment