Last active
September 20, 2024 13:26
-
-
Save romkatv/2a107ef9314f0d5f76563725b42f7cab to your computer and use it in GitHub Desktop.
Two-line ZSH prompt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?