Created
January 23, 2025 10:03
-
-
Save siddhpant/e6c40b42ff91bafe174303009d4667a4 to your computer and use it in GitHub Desktop.
Custom bash theme
This file contains hidden or 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
# SPDX-License-Identifier: MIT | |
# Copyright (c) 2025 Siddh Raman Pant <@siddhpant on GitHub> | |
# Design skeletal inspired from Kali Linux theme PS1: https://superuser.com/a/1781707 | |
# ----------------------------------------------------------------------------- | |
# Check add_starting_line() and add_ending_line() | |
____custom_ps1_separator="─" | |
# Colour codes. | |
____custom_ps1_colour_stop='\[\033[00m\]' | |
____custom_ps1_bold_yellow_start='\[\033[01;32m\]' # For separator. | |
____custom_ps1_bold_blue_start='\[\033[01;34m\]' # For dir. | |
____custom_ps1_bold_red_start='\[\033[01;31m\]' # For shell level. | |
____custom_ps1_red_start='\[\033[31m\]' # For command info. | |
____custom_ps1_green_start='\[\033[32m\]' # For command info. | |
# The text in parenthesis and brackets are not joiners. So stop the yellow | |
# color before the text, and start after the text. | |
____custom_ps1_left_parenthesis="($____custom_ps1_colour_stop" | |
____custom_ps1_right_parenthesis="$____custom_ps1_bold_yellow_start)" | |
____custom_ps1_left_square_bracket="[$____custom_ps1_colour_stop" | |
____custom_ps1_right_square_bracket="$____custom_ps1_bold_yellow_start]" | |
# ----------------------------------------------------------------------------- | |
# Save the command start time using PS0, so we can calculate the amount of time | |
# taken by a command, and also detect whether we actually executed a command or | |
# just pressed enter / did Ctrl+C / wrote a comment / etc. | |
____custom_ps1_previous_command_start_time='' | |
# Use parameter substitution trick to assign the value to our variable. | |
# We cannot use a simple function as that will execute in a subshell. | |
____custom_ps1_start_time_assigner='${____custom_ps1_previous_command_start_time:=${EPOCHREALTIME/[^0-9]/}}' | |
# Since the above results in a string, but we don't want it to be printed, | |
# let's use the replace parameter substitution trick on the same variable so | |
# that the assignment happens, but the initial empty string stays empty. | |
PS0='${____custom_ps1_previous_command_start_time//?/' | |
PS0+=$____custom_ps1_start_time_assigner | |
PS0+='}' | |
# ----------------------------------------------------------------------------- | |
____custom_ps1_format_duration_us() | |
{ | |
local us=$1 | |
local day=$(( us / 86400000000 )) | |
local hr=$(( us / 3600000000 % 24 )) | |
local min=$(( us / 60000000 % 60 )) | |
local sec=$(( us / 1000000 % 60 )) | |
local ms=$(( us / 1000 % 1000)) | |
local formatted_duration="" | |
if (( $day != 0 )); then formatted_duration+="${day}d "; fi | |
if (( $hr != 0 )); then formatted_duration+="${hr}h "; fi | |
if (( $min != 0 )); then formatted_duration+="${min}m "; fi | |
if [[ "$formatted_duration" == "" ]] && [[ "$sec" == "0" ]] ; then | |
formatted_duration+="${ms}ms" | |
else | |
printf -v ms "%03d" $ms | |
formatted_duration+="${sec}.${ms}s" | |
fi | |
echo $formatted_duration | |
} | |
____custom_ps1_exit_code_to_str() | |
{ | |
local exit_code_int=$1 | |
local exit_code_str="$exit_code_int" | |
case $exit_code_int in | |
1) exit_code_str="ERROR";; | |
2) exit_code_str="USAGE";; | |
64) exit_code_str="USAGE";; | |
65) exit_code_str="DATAERR";; | |
66) exit_code_str="NOINPUT";; | |
67) exit_code_str="NOUSER";; | |
68) exit_code_str="NOHOST";; | |
69) exit_code_str="UNAVAILABLE";; | |
70) exit_code_str="SOFTWARE";; | |
71) exit_code_str="OSERR";; | |
72) exit_code_str="OSFILE";; | |
73) exit_code_str="CANTCREAT";; | |
74) exit_code_str="IOERR";; | |
75) exit_code_str="TEMPFAIL";; | |
76) exit_code_str="PROTOCOL";; | |
77) exit_code_str="NOPERM";; | |
78) exit_code_str="CONFIG";; | |
126) exit_code_str="NOPERM";; | |
127) exit_code_str="NOTFOUND";; | |
# From output of `kill -l`, with codes 128 + n. | |
129) exit_code_str="SIGHUP";; | |
130) exit_code_str="SIGINT";; | |
131) exit_code_str="SIGQUIT";; | |
132) exit_code_str="SIGILL";; | |
133) exit_code_str="SIGTRAP";; | |
134) exit_code_str="SIGABRT";; | |
135) exit_code_str="SIGBUS";; | |
136) exit_code_str="SIGFPE";; | |
137) exit_code_str="SIGKILL";; | |
138) exit_code_str="SIGUSR1";; | |
139) exit_code_str="SIGSEGV";; | |
140) exit_code_str="SIGUSR2";; | |
141) exit_code_str="SIGPIPE";; | |
142) exit_code_str="SIGALRM";; | |
143) exit_code_str="SIGTERM";; | |
144) exit_code_str="SIGSTKFLT";; | |
145) exit_code_str="SIGCHLD";; | |
146) exit_code_str="SIGCONT";; | |
147) exit_code_str="SIGSTOP";; | |
148) exit_code_str="SIGTSTP";; | |
149) exit_code_str="SIGTTIN";; | |
150) exit_code_str="SIGTTOU";; | |
151) exit_code_str="SIGURG";; | |
152) exit_code_str="SIGXCPU";; | |
153) exit_code_str="SIGXFSZ";; | |
154) exit_code_str="SIGVTALRM";; | |
155) exit_code_str="SIGPROF";; | |
156) exit_code_str="SIGWINCH";; | |
157) exit_code_str="SIGIO";; | |
158) exit_code_str="SIGPWR";; | |
159) exit_code_str="SIGSYS";; | |
162) exit_code_str="SIGRTMIN";; | |
163) exit_code_str="SIGRTMIN+1";; | |
164) exit_code_str="SIGRTMIN+2";; | |
165) exit_code_str="SIGRTMIN+3";; | |
166) exit_code_str="SIGRTMIN+4";; | |
167) exit_code_str="SIGRTMIN+5";; | |
168) exit_code_str="SIGRTMIN+6";; | |
169) exit_code_str="SIGRTMIN+7";; | |
170) exit_code_str="SIGRTMIN+8";; | |
171) exit_code_str="SIGRTMIN+9";; | |
172) exit_code_str="SIGRTMIN+10";; | |
173) exit_code_str="SIGRTMIN+11";; | |
174) exit_code_str="SIGRTMIN+12";; | |
175) exit_code_str="SIGRTMIN+13";; | |
176) exit_code_str="SIGRTMIN+14";; | |
177) exit_code_str="SIGRTMIN+15";; | |
178) exit_code_str="SIGRTMAX-14";; | |
179) exit_code_str="SIGRTMAX-13";; | |
180) exit_code_str="SIGRTMAX-12";; | |
181) exit_code_str="SIGRTMAX-11";; | |
182) exit_code_str="SIGRTMAX-10";; | |
183) exit_code_str="SIGRTMAX-9";; | |
184) exit_code_str="SIGRTMAX-8";; | |
185) exit_code_str="SIGRTMAX-7";; | |
186) exit_code_str="SIGRTMAX-6";; | |
187) exit_code_str="SIGRTMAX-5";; | |
188) exit_code_str="SIGRTMAX-4";; | |
189) exit_code_str="SIGRTMAX-3";; | |
190) exit_code_str="SIGRTMAX-2";; | |
191) exit_code_str="SIGRTMAX-1";; | |
192) exit_code_str="SIGRTMAX";; | |
esac | |
echo $exit_code_str | |
} | |
# ----------------------------------------------------------------------------- | |
# Initialise PS1 here so we can set it in functions later. | |
# This is the only place where it will be an empty string. | |
# So it will be empty only before the first prompt of the terminal window. | |
PS1='' | |
____custom_ps1_previous_command_exit_status='' | |
____custom_ps1_show_previous_command_info() | |
{ | |
____custom_ps1_previous_command_exit_status=$? | |
# First prompt of terminal doesn't need to account for last command. | |
if [[ "$PS1" == '' ]] && (( $SHLVL == 1 )); then | |
return | |
fi | |
# We don't need to account for "previous" command if the last prompt | |
# had no command execution. In that case, there will be no PS0 and thus | |
# no previous_command_start_time. | |
if [[ $____custom_ps1_previous_command_start_time == '' ]]; then | |
# Add a newline regardless. | |
PS1="\n" | |
return | |
fi | |
# Save previous info in local variables for better naming. | |
local previous_start_time=$____custom_ps1_previous_command_start_time | |
local previous_exit_status=$( | |
____custom_ps1_exit_code_to_str \ | |
$____custom_ps1_previous_command_exit_status | |
) | |
# Reset global variables now itself. | |
PS1='' | |
____custom_ps1_previous_command_start_time='' | |
# If the last command didn't end with a newline (like a printf), then | |
# our right prompt for info will get messed up. So in that case, we | |
# will add a new line and just make it explicit like GitHub that there | |
# is no newline using a symbol. | |
local row_pos | |
local col_pos | |
local newline_start=" " | |
# https://unix.stackexchange.com/a/183121 | |
IFS=';' read -sdR -p $'\E[6n' row_pos col_pos | |
if [[ "${col_pos#*[}" != "1" ]]; then | |
# In the above check, we stripped escape codes from col_pos. | |
PS1+="\n" | |
newline_start=$____custom_ps1_bold_red_start | |
newline_start+="⊖" | |
newline_start+=$____custom_ps1_colour_stop | |
fi | |
local current_time=${EPOCHREALTIME/[^0-9]/} | |
local runtime_us=$(( current_time - previous_start_time )) | |
local time_taken_str=$(____custom_ps1_format_duration_us $runtime_us) | |
local info="" | |
local info_colour="" | |
if [[ "$previous_exit_status" == "0" ]]; then | |
info_colour=$____custom_ps1_green_start | |
info="[$time_taken_str]" | |
else | |
info_colour=$____custom_ps1_red_start | |
info="[$previous_exit_status / $time_taken_str]" | |
fi | |
# Right align info (acounting for extra char we will add later). | |
printf -v info "%$(( $COLUMNS - 1 ))s" "$info" | |
PS1+=$newline_start | |
PS1+=$info_colour | |
PS1+=$info | |
PS1+=$____custom_ps1_colour_stop | |
PS1+="\n" | |
} | |
____custom_ps1_add_starting_line() | |
{ | |
PS1+=$____custom_ps1_bold_yellow_start | |
PS1+='┌──' | |
PS1+='${debian_chroot:+($debian_chroot)}' | |
} | |
____custom_ps1_add_ending_line() | |
{ | |
PS1+='\n' | |
PS1+='└─' | |
PS1+=$____custom_ps1_colour_stop | |
PS1+='\$ ' # \$ is an escape sequence for PS1 (# if UID 0, else $). | |
} | |
____custom_ps1_add_shell_level() | |
{ | |
if (( $SHLVL == 1 )); then | |
return | |
fi | |
PS1+=$____custom_ps1_separator | |
PS1+=$____custom_ps1_left_parenthesis | |
PS1+=$____custom_ps1_bold_red_start | |
PS1+=" Lvl $SHLVL" | |
PS1+=$____custom_ps1_colour_stop | |
PS1+=$____custom_ps1_right_parenthesis | |
} | |
____custom_ps1_add_pwd() | |
{ | |
PS1+=$____custom_ps1_separator | |
PS1+=$____custom_ps1_left_square_bracket | |
PS1+=$____custom_ps1_bold_blue_start | |
PS1+='\w' | |
PS1+=$____custom_ps1_colour_stop | |
PS1+=$____custom_ps1_right_square_bracket | |
} | |
____custom_ps1_add_git() | |
{ | |
____custom_ps1_git_ps1="$( | |
GIT_PS1_SHOWDIRTYSTATE=1 | |
GIT_PS1_SHOWSTASHSTATE=1 | |
GIT_PS1_SHOWCONFLICTSTATE=1 | |
GIT_PS1_SHOWUNTRACKEDFILES=1 | |
GIT_PS1_SHOWUPSTREAM="verbose" | |
GIT_PS1_DESCRIBE_STYLE=default | |
__git_ps1 | |
)" | |
if [[ "$____custom_ps1_git_ps1" == "" ]]; then | |
return | |
fi | |
# Remove starting space and parentheses. | |
____custom_ps1_git_ps1=${____custom_ps1_git_ps1:2:-1} | |
PS1+=$____custom_ps1_separator | |
PS1+=$____custom_ps1_left_square_bracket | |
PS1+=$____custom_ps1_git_ps1 | |
PS1+=$____custom_ps1_right_square_bracket | |
} | |
____custom_ps1_build_ps1() | |
{ | |
____custom_ps1_show_previous_command_info | |
____custom_ps1_add_starting_line | |
____custom_ps1_add_shell_level | |
____custom_ps1_add_pwd | |
____custom_ps1_add_git | |
____custom_ps1_add_ending_line | |
} | |
PROMPT_COMMAND="____custom_ps1_build_ps1" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment