commit 12e96c1088ca6434ce1cedb222f03be0668aa463 Author: Tomasz Kapias Date: Sat Mar 18 18:49:08 2023 +0700 first commit diff --git a/.bash_logout b/.bash_logout new file mode 100644 index 0000000..de4f5f7 --- /dev/null +++ b/.bash_logout @@ -0,0 +1,7 @@ +# ~/.bash_logout: executed by bash(1) when login shell exits. + +# when leaving the console clear the screen to increase privacy + +if [ "$SHLVL" = 1 ]; then + [ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q +fi diff --git a/.bash_profile b/.bash_profile new file mode 100644 index 0000000..461aade --- /dev/null +++ b/.bash_profile @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +. "$HOME/.profile" diff --git a/.bashrc b/.bashrc new file mode 100644 index 0000000..535c5e0 --- /dev/null +++ b/.bashrc @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# ~/.bashrc: executed by bash(1) for non-login shells. + +# If not running interactively, don't do anything +case $- in + *i*) ;; + *) return ;; +esac + +chmod 700 ~/.bashrc.d +chmod 600 ~/.bashrc ~/.bashrc.d/* + +for file in ~/.bashrc.d/*.bashrc; +do + source "${file}" +done diff --git a/.bashrc.d/00-shelloptions.bashrc b/.bashrc.d/00-shelloptions.bashrc new file mode 100644 index 0000000..71fe8c7 --- /dev/null +++ b/.bashrc.d/00-shelloptions.bashrc @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +# ============================== +# SHELL OPTIONS +# ============================== + +# shell command line style : emacs, vi, noediting +set -o emacs +# set -o vi +# set –noediting + +# exit will lists the status interactive jobs and postpone if one is running +shopt -s checkjobs + +# includes filenames beginning with a . (dot) in pathname expansion +shopt -s dotglob + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# enable programmable completion features +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi + +# activate a DEBUG trap to catch term window resize +#declare -x rows cols +#update_size(){ +# winsizeStatus=$(shopt | grep checkwinsize | awk '{print $2}') +# if [[ "$winsizeStatus" == "off" ]]; then +# rows=$(tput lines) +# cols=$(tput cols) +# echo -en "\e[2;5;90mDEBUG\e[25;90m TERM winsize updated to ${rows}x${cols} >\e[0m" +# else +# echo -en "\e[2;5;90mDEBUG\e[25;90m TERM winsize updated to ${LINES}x${COLUMNS} >\e[0m" +# fi +#} +#trap update_size WINCH + +# ============================== +# HISTORY +# ============================== + +# attempts to save all lines of a multiple-line command in the same history entry +shopt -s cmdhist + +# appended rather than overwrite +shopt -s histappend + +# with cmdhist, saved with embedded newlines rather than semicolon separators +shopt -s lithist + +HISTCONTROL=ignoreboth +HISTSIZE=10000 +HISTFILESIZE=20000 +HISTTIMEFORMAT="%y/%m/%d %T " +HISTIGNORE="history:ls:l:pwd:exit:" +if [[ ${BASH_VERSION:0:1} -gt 5 || ${BASH_VERSION:0:1} -ge 5 && ${BASH_VERSION:2:1} -ge 1 ]]; then + PROMPT_COMMAND=("history -a" "history -c" "history -r") +else + PROMPT_COMMAND="history -a; history -c; history -r" +fi diff --git a/.bashrc.d/01-env.bashrc b/.bashrc.d/01-env.bashrc new file mode 100644 index 0000000..0bc2694 --- /dev/null +++ b/.bashrc.d/01-env.bashrc @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +# ============================== +# ENV +# ============================== + +# ============================== +# System +# ============================== + +# Expanding PATH with system's bins +if [[ -d "/usr/sbin" ]] ; then + export PATH="/usr/sbin:$PATH" +fi +if [[ -d "/usr/local/sbin" ]] ; then + export PATH="/usr/local/sbin:$PATH" +fi + +# Expanding PATH with users bin +if [[ -d "$HOME/.local/bin" ]] ; then + export PATH="$HOME/.local/bin:$PATH" +fi + +# Check if XDG environment variables are set or set them to default +if [[ -z "$XDG_DATA_HOME" ]]; then + export XDG_DATA_HOME=$HOME/.local/share +fi +if [[ -z "$XDG_CONFIG_HOME" ]]; then + export XDG_CONFIG_HOME=$HOME/.config +fi +if [[ -z "$XDG_STATE_HOME" ]]; then + export XDG_STATE_HOME=$HOME/.local/state +fi +if [[ -z "$XDG_CACHE_HOME" ]]; then + export XDG_CACHE_HOME=$HOME/.cache +fi + +# Check presence of locale Download folder or create a default one +if [[ "$(xdg-user-dir DOWNLOAD)" == "$(xdg-user-dir)" ]]; then + mkdir -p "$HOME/Downloads" + export XDG_DOWNLOAD_DIR="$HOME/Downloads" +else + XDG_DOWNLOAD_DIR="$(xdg-user-dir DOWNLOAD)" + export XDG_DOWNLOAD_DIR +fi + +# ============================== +# Shell +# ============================== + +# add a preview for commands and a toggle bind for FZF's history +if command -v fzf 1> /dev/null; then + export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:4:wrap --bind 'ctrl-p:toggle-preview'" +fi + +# vim a global text editor +if command -v vim 1> /dev/null; then + export EDITOR=vim +fi + +# ============================== +# Languages +# ============================== + +# Cargo env config +if [[ -d "$HOME/.cargo/bin" ]]; then + export PATH="$HOME/.cargo/bin:$PATH" +fi + +# Nim env config +if [[ -d "$HOME/.nimble/bin" ]]; then + export PATH="$HOME/.nimble/bin:$PATH" +fi + +# Go env config +if [[ -d "$HOME/go/bin" ]]; then + export PATH="$HOME/go/bin:$PATH" +fi + +# Deno env config +if [[ -f "$HOME/.deno/bin/deno" ]]; then + export DENO_INSTALL="$HOME/.deno" + export PATH="$DENO_INSTALL/bin:$PATH" +fi + +# Expanding PYTHONPATH for PDM +if command -v pdm 1> /dev/null; then + # update with output from here when upgrading Python : eval "$(pdm --pep582)" + if [[ -n "$PYTHONPATH" ]] && [[ -d "/usr/lib/python3/dist-packages/pdm/pep582" ]]; then + export PYTHONPATH='/usr/lib/python3/dist-packages/pdm/pep582':$PYTHONPATH + elif [[ -d "/usr/lib/python3/dist-packages/pdm/pep582" ]]; then + export PYTHONPATH='/usr/lib/python3/dist-packages/pdm/pep582' + fi +fi + +# PIPX env config +if command -v pipx 1> /dev/null; then + eval "$(register-python-argcomplete pipx)" +fi + +# refuser l'adhésion à la télémétrie .NET +if command -v dontnet 1> /dev/null; then + export DOTNET_CLI_TELEMETRY_OPTOUT=1 +fi + +# ============================== +# Applications +# ============================== + +# Enable key bindings for fzf +if command -v fzf 1> /dev/null && [[ -f "/usr/share/doc/fzf/examples/key-bindings.bash" ]]; then + source /usr/share/doc/fzf/examples/key-bindings.bash +fi + +# Let gpg-agent know from which terminal its has been called. +if command -v gpg 1> /dev/null; then + export GPG_TTY=$(tty) +fi + +# set SSH Agent socket for KeepassXC +if [[ -z "$SSH_CONNECTION" ]] && command -v keepassxc 1> /dev/null; then + SSH_AUTH_SOCK=$(find /tmp/ -path "/tmp/ssh-*/agent.*" -printf "%TY-%Tm-%Td_%TH:%TM:%TS %p\n" 2>/dev/null | sort -n | tail -1 | awk '{print $2}') + export SSH_AUTH_SOCK +fi + +# preprocessor for less used in neomutt +if command -v lesspipe.sh 1> /dev/null; then + export LESSOPEN="|LESSQUIET=1 /usr/local/bin/lesspipe.sh %s" + export MAGIC="/usr/share/file/magic:$HOME/.local/share/file/magic" +fi + +# additional cfgs for hl +if command -v hl 1> /dev/null; then + export HL_CONF=$HOME/.config/hl/config.d:/etc/hl/config.d +fi + +# used by functions mark/unmark/marks/jump +if [[ -d "$HOME/.marks" ]]; then + export MARKPATH="$HOME/.marks" +else + mkdir "$HOME/.marks" + export MARKPATH="$HOME/.marks" +fi + +# ============================== +# DISPLAY +# ============================== + +#some fancy, colorful manpages +export LESS_TERMCAP_so=$(printf '\e[01;33m') # enter standout mode – yellow +export LESS_TERMCAP_se=$(printf '\e[0m') # leave standout mode +export LESS_TERMCAP_us=$(printf '\e[04;36m') # enter underline mode – cyan +export LESS_TERMCAP_ue=$(printf '\e[0m') # leave underline mode +export LESS_TERMCAP_mb=$(printf '\e[05;31m') # enter blinking mode – red +export LESS_TERMCAP_md=$(printf '\e[01;35m') # enter double-bright mode – bold, magenta +export LESS_TERMCAP_me=$(printf '\e[0m') # turn off all appearance modes (mb, md, so, us) + +# colored GCC warnings and errors +export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' diff --git a/.bashrc.d/02-secrets.bashrc b/.bashrc.d/02-secrets.bashrc new file mode 100644 index 0000000..a490fe8 --- /dev/null +++ b/.bashrc.d/02-secrets.bashrc @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +# ============================== +# SECRETS +# ============================== + +# for the boop function +NTFYSERVER="ntfy.server.tld" +export NTFYBOOPSURL="https://${NTFYSERVER}/boops" +if command -v keyring 1> /dev/null && [[ -n "$DBUS_SESSION_BUS_ADDRESS" ]]; then + NTFYBOOPSTOKEN="$(keyring -b keyring.backends.SecretService.Keyring get ${NTFYSERVER} boop-token)" + export NTFYBOOPSTOKEN +fi + +# locale city for scripts like meteo alias +export MYCITY="Some City" diff --git a/.bashrc.d/10-aliases.bashrc b/.bashrc.d/10-aliases.bashrc new file mode 100644 index 0000000..b4aa2ee --- /dev/null +++ b/.bashrc.d/10-aliases.bashrc @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +# ============================== +# ALIASES +# ============================== + +# ============================== +# Overlays +# ============================== + +if command -v dircolors 1> /dev/null; then + alias dir='dir --color=auto' + alias vdir='vdir --color=auto' + + # had to add -a because grep interprete a part of syslog as a binary + alias grep='grep --color=auto -a' + alias fgrep='fgrep --color=auto' + alias egrep='egrep --color=auto' + + alias ls='ls --color=auto' +else + alias grep='grep -a' +fi + +# most common usage of df/du +if command -v hl 1> /dev/null; then + alias df='df -Tha --total | hlauto --df' +else + alias df='df -Tha --total' +fi +alias du='du -ach' + +# better ncdu +if command -v ncdu 1> /dev/null; then + alias ncdu='ncdu -e --exclude-kernfs --color dark' +fi + +# nano with backups +if command -v nano 1> /dev/null; then + mkdir -p ~/.cache/nano/backups + alias nano='nano -BGSceilqy% --atblanks -T 4 -C ~/.cache/nano/backups' + # clean nano backups + alias nano-clean='rm -rf ~/.cache/nano/backups/*' +fi +# force vim usage +if command -v vim 1> /dev/null; then + alias vi='vim' +fi + +# ============================== +# New/Updated Utils +# ============================== + +# login to root with sudo but keeping your ssh-agent and tmux env +alias sudoi='sudo -i --preserve-env=TMUX,WINDOWID,SSH_AGENT_PID,TERM_PROGRAM' + +# short cd .. +alias cd..='cd ..' +alias ..='cd ..' +# last location +alias back='cd "$OLDPWD"' + +# new upgraded ls, check fzf versions in functions +if command -v exa 1> /dev/null; then + alias l='exa -abghHl --git --icons --color always --group-directories-first --time-style=long-iso' +fi + +# most common usage of ps +alias psf='ps faux' +# grep in psf +alias psg='ps faux | grep -v grep | grep -i -e VSZ -e' + +# grep in history +alias hgrep='history | grep -i' +# print the 15 last lines from history +alias recent='fc -l' + +# mkdir verbose and recursive +alias mkd='mkdir -pv' + +# better man +alias manu='man --encoding UTF-8 --no-hyphenation --pager "less --ignore-case --LONG-PROMPT --mouse --quit-at-eof --quit-if-one-screen --RAW-CONTROL-CHARS --squeeze-blank-lines --status-column --tilde --use-color"' + +# less with follow like tail -f +alias less-follow='less --auto-buffers --follow-name +F --ignore-case --LONG-PROMPT --mouse --quit-at-eof --RAW-CONTROL-CHARS --squeeze-blank-lines --status-column --tilde --use-color' + +# find alternative +if command -v fdfind 1> /dev/null; then + alias fd='fdfind' + alias fdh='fdfind -H' +fi + +# shape the bandwidth allowd for a command with trickle: standalone, D:100KB/s, U10KB/s +if command -v trickle 1> /dev/null; then + alias slow='trickle -s -d 100 -u 10 -t 1' +fi + +if command -v xclip 1> /dev/null; then + # shortcuts to copy/paste text contents + alias xclip-in-txt='xclip -in -rmlastnl -selection clipboard' + alias xclip-txt-out='xclip -out -rmlastnl -selection clipboard' + # shortcuts to copy/paste other contents + alias xclip-in-png='xclip -in -selection clipboard -t image/png' + alias xclip-in-jpeg='xclip -in -selection clipboard -t image/jpeg' + alias xclip-in-gif='xclip -in -selection clipboard -t image/gif' + alias xclip-in-webp='xclip -in -selection clipboard -t image/webp' + alias xclip-in-svg='xclip -in -selection clipboard -t image/svg' + alias xclip-in-ico='xclip -in -selection clipboard -t image/ico' + alias xclip-in-tiff='xclip -in -selection clipboard -t image/tiff' + alias xclip-out-png='xclip -out -selection clipboard -t image/png' + alias xclip-out-jpeg='xclip -out -selection clipboard -t image/jpeg' + alias xclip-out-gif='xclip -out -selection clipboard -t image/gif' + alias xclip-out-webp='xclip -out -selection clipboard -t image/webp' + alias xclip-out-svg='xclip -out -selection clipboard -t image/svg' + alias xclip-out-ico='xclip -out -selection clipboard -t image/ico' + alias xclip-out-tiff='xclip -out -selection clipboard -t image/tiff' +fi + +# ps alternative on rust +if command -v procs 1> /dev/null; then + alias procs='procs --insert VmRss --insert VmSwap --insert Threads --insert Priority --insert ElapsedTime --insert State --sortd VmSwap' +fi + +# ============================== +# New commands or external programs +# ============================== + +# custom motd dashboard +if [[ -f "/etc/update-motd.d/00-motdfetch" ]]; then + alias motd='/etc/update-motd.d/00-motdfetch' +fi + +# SSH and ET with MOTD +alias sshmotd='ssh -o SetEnv=SSH_MOTD=1' +if command -v et 1> /dev/null; then + alias etmotd='et --ssh-option SetEnv=SSH_MOTD=1' +fi + +# open a new tmux session named ssh_tmux or attach to it +if command -v tmux 1> /dev/null; then + alias tmuxs='tmux new-session -A -s ssh_tmux && exit' +fi + +# vifm: tui file explorer +if command -v vifmrun 1> /dev/null && command -v vifm 1> /dev/null; then + alias vifm='vifmrun' + alias vf='vifmrun' +fi + +# dragon for drag&drop function +if command -v dragon 1> /dev/null; then + alias drag='dragon --and-exit --on-top' + alias drop='dragon --and-exit --on-top --target' +fi + +# glow: tui/cli markdown pager explorer +if command -v glow 1> /dev/null; then + alias glw='glow -l -p -w 80 -s dark' +fi + +if command -v tidy-viewer 1> /dev/null; then + alias tv='tidy-viewer' +fi + +# HLEDGER +if command -v hledger 1> /dev/null; then + export LEDGER_FILE=$HOME/.finance/$(date +"%Y").journal +fi + +# get local meteo for 3 days +alias meteo='echo -e "┌─────────┐\n│ wttr.in │ ${MYCITY}:\n└─────────┘\n" && curl "wttr.in/$(echo ${MYCITY} | tr " -" "+")?mM2FQ&lang=${LANGUAGE/*:}"' + +# mimic3: tts interactive command, just paste text inside +if command -v mimic3 1> /dev/null; then + alias mimic3-fr='mimic3 --voice fr_FR/tom_low --play-program paplay --interactive' + alias mimic3-en='mimic3 --voice en_US/ljspeech_low --play-program paplay --interactive' + alias mimic3-pl='mimic3 --voice pl_PL/m-ailabs_low --play-program paplay --interactive' +fi + +# Neomutt, notmuch, mbsync +if command -v neomutt 1> /dev/null; then + alias ntt='neomutt -f "📫Boite à lettres"' + if command -v notmuch 1> /dev/null && command -v mbsync 1> /dev/null; then + # Sync all Mailboxes, Notmuch and Afew, then launch Neomutt + alias ntt-sync='notmuch new && mbsync mutt && notmuch new && neomutt -f "📫Boite à lettres"' + # Sync some Mailboxes, Notmuch and Afew, then launch Neomutt + alias ntt-sync-base='notmuch new && mbsync mutt-fast && notmuch new && neomutt -f "📫Boite à lettres"' + # Reindex Notmuch and Afew for main, then launch Neomutt + alias ntt-index='notmuch new && neomutt -f "📫Boite à lettres"' + fi + if command -v msmtp-queue 1> /dev/null; then + # Flush send mail queue with msmtpq, then launch Neomutt + alias ntt-flush='msmtp-queue -r && neomutt -f "📫Boite à lettres"' + fi +fi + +# Display webcam output with MPV +if command -v mpv 1> /dev/null && [[ -c "/dev/video0" ]]; then + alias mpv-webcam='mpv av://v4l2:/dev/video0 --profile=low-latency --untimed' + if [[ -c "/dev/video2" ]]; then + alias mpv-fakecam='mpv av://v4l2:/dev/video2 --profile=low-latency --untimed' + fi +fi + +# easy downloads with yt-dlp +if command -v yt-dlp 1> /dev/null && command -v ffmpeg 1> /dev/null; then + alias yt-audio-low='yt-dlp --no-update --socket-timeout 50 --force-ipv4 --limit-rate 1M --downloader wget --paths ${XDG_DOWNLOAD_DIR} --quiet --progress --prefer-free-formats --check-formats --format worstaudio' + alias yt-video-medium='yt-dlp --no-update --socket-timeout 50 --force-ipv4 --limit-rate 1M --downloader wget --paths ${XDG_DOWNLOAD_DIR} --quiet --progress --prefer-free-formats --check-formats --format "bestvideo[height<=?360][vbr>100][vbr<500]+worstaudio"' +fi + +# to reconnect by tcp:5555 to the adb device currently tethering +if command -v adb 1> /dev/null; then + alias adb-reconnect='adb disconnect; adb connect $(ip route | egrep "^default" | awk "{print \$3}"):5555' +fi diff --git a/.bashrc.d/11-functions.bashrc b/.bashrc.d/11-functions.bashrc new file mode 100644 index 0000000..ff472ed --- /dev/null +++ b/.bashrc.d/11-functions.bashrc @@ -0,0 +1,715 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +# ============================== +# FUNCTIONS +# ============================== + +# ============================== +# System +# ============================== + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# pass declared functions to sudo, /!\ aliases in the function will not work +sudof() { + + Help() { + cat <<- HEREDOC + + Pass declared functions as command to sudo bash. + Syntax: sudof [-h] [] + options: + -h Print this Help. + + HEREDOC + } + while getopts ":h" option; do + case $option in + h) Help; return 0 ;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + + local TMPFUNC=$(declare -f "$1") + local TMPCMD=${@:1} + sudo bash -c "$TMPFUNC; $TMPCMD" +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Pretty print dependencies from a package with dpkg +dpkg-debdep() { + dpkg --info "$1" | \ + awk 'BEGIN{print "Depends:"} \ + /Depends: / { gsub("Depends: ", ""); \ + n=split($0,deps,","); \ + for(i=1;i<=n;i++) print deps[i] \ + }' +} + +# ============================== +# Terminal +# ============================== + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# set of functions for marks/jump, MARKPATH created/exported in 01.env.bashrc +mark() { ln -s "$(pwd)" "$MARKPATH/$1"; } +unmark() { rm -i "$MARKPATH/$1"; } +marks() { ls -l "$MARKPATH" | sed 's/ / /g' | cut -d' ' -f9- | sed 's/ -/\t-/g' && echo; } +jump() { cd -P "$MARKPATH/$1" 2>/dev/null || echo "No such mark: $1"; } + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# pipe to hl only if you are in a tty and the output is not piped or redirected +if command -v hl 1> /dev/null; then + hlauto() { + if [ -t 1 ]; then + hl "$1" + else + cat - + fi + } +fi + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# full page of \n for the clear alias +pgdown() { + printf '\n%.0s' $(eval echo {1..$(( $(tput lines) - 1 ))}) +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# clear teminal and TMUX history and get to the bottom line +c() { + echo -en "\ec"; pgdown + if [[ -n $TMUX_PANE ]]; then + tmux clear-history -t "$TMUX_PANE" + fi +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# shortcut for tmux to open a new default session with a name or attach if it exist +# an alias tmuxs exist too for ssh sessions +if command -v tmux 1> /dev/null; then + tmuxa() { + systemd-run -q --scope --user tmux new-session -A -s default "$@" + exit + } +fi + +# ============================== +# Utils +# ============================== + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# mkdir with sub-dirs and change of pwd +md() { mkdir -pv "$1" && cd "$1"; } + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# list almost all services units (except user disabled) with state color +lservices() { + Help() { + cat <<- HEREDOC + + List all Systemd services units. + Simplified and colorful. + + Syntax: lservices [-h] [-x] [] + Output: ServiceName UID STATE DESCRIPTION + options: + -h Print this Help. + -x Copy the list to Clipboard with xclip. + -p Disable the pager feature. + [root] display --system services, + [other] display --user services, + [empty] default to current user. + + HEREDOC + } + + local OPTIND=0 + while getopts ":hxp" option; do + case $option in + h) Help; return 0 ;; + x) local _XCLIP=1;; + p) local _NOPAGER=1;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + shift $((OPTIND-1)) + + if [[ -z $1 ]]; then + local _UID=$(id -u) + else + local _UID=$(id -u $1) + fi + + if [[ "$_UID" == "0" ]]; then + local _OUT=$(sudo systemctl list-units --system -q --plain --full --no-pager -t service) + elif [[ "$_UID" == "$(id -u)" ]]; then + local _OUT=$(systemctl list-units --user -q --plain --full --no-pager -t service) + else + local _OUT=$(sudo machinectl -q shell --uid="$_UID" .host /usr/bin/systemctl list-units --user -q --plain --full --no-pager -t service) + fi + + if [[ $_XCLIP == 1 ]]; then + echo -e "${_OUT}" | xclip -selection clipboard + fi + + local _OUT=$(echo "$_OUT" | awk -v uid=$_UID ' \ + BEGIN { + red = "\033[1;31m" + green = "\033[1;32m" + yellow = "\033[1;33m" + blue = "\033[1;36m" + reset = "\033[0m" + + map["running"] = green + map["exited"] = yellow + map["failed"] = red + } + match($0,/^(.*)\.service(\s*)loaded\s[a-z]+\s([a-z]+)(\s.*)$/,a) { + status = a[3] + if ( status in map ) + $0 = blue a[1] a[2] reset " " uid " " map[status] status reset a[4] + else + $0 = blue a[1] a[2] reset " " uid " " a[3] a[4] + } + { print } ' + ) + + if [[ $_NOPAGER == 1 ]]; then + echo -e "${_OUT}" + else + echo -e "${_OUT}" | less --ignore-case --LONG-PROMPT --mouse --RAW-CONTROL-CHARS --tilde --use-color --quit-if-one-screen + fi + + if [[ $_XCLIP == 1 ]]; then + echo "List copied in the system clipboard!" + fi +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# bat/cat-like with no comments +if command -v bat 1> /dev/null; then + batl() { + bat -pp --color always "$@" \ + | sed -E -e '1b;/^(\x1B\[[0-9;]+m\s*\x1B\[[0-9;]+m\x1B\[[0-9;]+m)?(\s+)?(\x1B\[[0-9;]+m)?(#|\")/d' \ + | bat --decorations never --pager "less --ignore-case --status-column --line-numbers --quiet --tilde --use-color --quit-if-one-screen --RAW-CONTROL-CHARS" + } +fi + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# custom alternative to ls that pipe to fzf-tmux or fzf +lf() { + Help() { + cat <<- HEREDOC + + A custom alternative to ls that pipe to fzf-tmux or fzf + Accept only the first path submitted (Default=.) + + Syntax: lf [-h] [-a] [-e] [-o] [-t -l level] [] + options: + -h Print this Help. + -a Display hidden files + -e Sort by extension + -o Sort by oldest + -t Tree mode + -l num Tree level, default=2 + + HEREDOC + } + + local _type="list" + local _filter="visible" + local _by="name" + local _lvl="" + local _fzf_opt="--preview-window down:5:wrap" + + local OPTIND=0 + while getopts ":haeotl:" option; do + case $option in + h) Help; return 0 ;; + a) local _all="-a" + local _filter="all";; + e) local _sort="--sort=extension" + local _by="ext" ;; + o) local _sort="--sort=oldest" + local _by="oldest" ;; + t) local _tree="-T" + local _type="tree" + local _fzf_opt="--keep-right --preview-window down:2:wrap" + local _lvl=", with 2 level," ;; + l) local _level="-L ${OPTARG}" + local _lvl=", with ${OPTARG} level," ;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + shift $((OPTIND-1)) + + if [[ -f "/usr/bin/tmux" || -f "/usr/local/bin/tmux" ]]; then + local _fzfCmd="fzf-tmux -p 95%,75%" + else + local _fzfCmd="fzf" + fi + + if [ -z "$1" ]; then + local _location="$(realpath -sq "$PWD" 2>&1)" + elif [ -z "$2" ]; then + local _location="$(realpath -sq "$1" 2>&1)" + else + echo "Error: Invalid number of paths" + Help + return 0 + fi + + local _localizer="awk -v loc=\"$_location/\" '{print \"\\\"\" loc \$0 \"\\\"\"}'" + local _oneliner="sed -E -e 's/\s->\s.+//g' \ + | awk '{\$1=\$2=\$3=\$4=\$5=\$6=\$7=\"\"; \$0=\$0; \$1=\$1; print \$0}' \ + | sed -E -e 's/^[^[:digit:][:alpha:]_.]*\s//g' \ + | awk -v loc=\"$_location/\" '{print \"\\\"\" loc \$0 \"\\\"\"}' \ + | xargs -d \"\n\"" + if [[ "$_type" == "tree" ]]; then + local _localizer="awk '{print \"\\\"\" \$0 \"\\\"\"}'" + local _oneliner="cat" + fi + + exa -bghHl $_all $_sort $_tree $_level --icons --git --color always --group-directories-first --time-style=long-iso "$_location" 2>&1 \ + | ${_fzfCmd} -p 95%,75% --multi --reverse --ansi --tabstop 2 --header-lines 1 \ + --prompt "A $_type$_lvl of $_filter elements sorted by $_by in \"$_location\" > " \ + $_fzf_opt --bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all,ctrl-p:toggle-preview \ + --preview "for line in {+}; do echo \"\$line\"; done \ + | sed -E -e 's/\s->\s.+//g' \ + | awk '{\$1=\$2=\$3=\$4=\$5=\$6=\$7=\"\"; \$0=\$0; \$1=\$1; print \$0}' \ + | sed -E -e 's/^[^[:digit:][:alpha:]_.]*\s//g' \ + | $_localizer \ + | xargs -d \"\n\"" \ + | eval "$_oneliner" +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# find file/symlinks or directories with fd and fzf, use a regex pattern and an optionnal location +fdf() { + local array=( fd exa fzf bat ) + for cmd in "${array[@]}"; do + if [[ -z $(command -v $cmd) ]]; then + echo 'function requirements: $cmd could not be found: sudo apt install fd-find exa fzf bat' + return 0 + fi + done + + Help() { + cat <<- HEREDOC + + Filesystem search shortcuts with fd, fzf bat and exa + + Syntax: fdf [-h] [-d] [-p] [-e] "" [] + options: + -h Print this Help. + -d search directories only (defaut to files & symlinks) + -p preview in fzf (bat for files and exa tree for dirs) + -e output results with exa + + HEREDOC + } + + local _fdfindType="-tf -tl" + if [[ -f "/usr/bin/tmux" || -f "/usr/local/bin/tmux" ]]; then + local _fzfCmd="fzf-tmux" + else + local _fzfCmd="fzf" + fi + local _previewCdm="bat --style=numbers --color=always --line-range :150 {}" + local _xargs="cat" + + local OPTIND=0 + while getopts ":hdpe" option; do + case $option in + h) Help; return 0 ;; + d) local _fdfindType="-td" + local _exaType="-d" + local _fzfHeaderType="dir" + local _previewCdm="exa -bhlaTL 2 --icons --color always --group-directories-first --no-permissions --time-style=iso {}" ;; + p) local _fzfCmd="fzf" + local _preview=$_previewCdm ;; + e) local _xargs="xargs -r exa -abghHl --icons --color always --group-directories-first --time-style=long-iso" ;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + shift $((OPTIND-1)) + if [ -z "$1" ]; then + echo "Error: fdf needs a " + Help + return 0 + fi + + if [ -z "$2" ]; then + local _searchPath="$(realpath -sq "$PWD" 2>&1)" + else + local _searchPath="$(realpath -sq "$2" 2>&1)" + fi + + fdfind -0aH $_fdfindType ^$1\$ "$_searchPath" | xargs -r -0 exa $_exaType --colour=always \ + | $_fzfCmd -m --reverse --ansi --keep-right --bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all --header "^$1\$ $_fzfHeaderType in \"$_searchPath\"" --preview "$_preview" | eval "$_xargs" +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# better listing for all tcp/udp sockets +ssfull() { + if [[ -f "/usr/bin/tmux" || -f "/usr/local/bin/tmux" ]]; then + local _fzfCmd="fzf-tmux" + else + local _fzfCmd="fzf" + fi + ss -Haptun --cgroup 2>&1 \ + | awk '{gsub(/cgroup:/,"- - cgroup:",$7);if($7 =="")print $0" - - -";else print $0;}' \ + | awk '{ + gsub(/,fd=.+,pid=/,",",$7);gsub(/users:|\(|\)|,fd=[0-9]+/,"",$7);gsub(/,pid=/," ",$7); + gsub(/.+\/|cgroup:/,"",$8); + gsub(/.+\/|cgroup:/,"",$9); + if(match($5, /^.+%[^\]]+:.+$/))localip = gensub(/^(.*[0-9\]*])%.+:[0-9*]{1,5}$/,"\\1","g",$5);else localip = gensub(/^(.*):[0-9*]{1,5}$/,"\\1","g",$5); + if(match($5, /^.+%[^\]]+:.+$/))localindex = gensub(/^.+%([^\]]+):.+$/,"\\1","g",$5);else localindex = "-"; + localport = gensub(/^.*:([^:]*)$/,"\\1","g",$5); + if(match($6, /^.+%[^\]]+:.+$/))peerip = gensub(/^(.*[0-9\]*])%.+:[0-9*]{1,5}$/,"\\1","g",$6);else peerip = gensub(/^(.*):[0-9*]{1,5}$/,"\\1","g",$6); + if(match($6, /^.+%[^\]]+:.+$/))peerindex = gensub(/^.+%([^\]]+):.+$/,"\\1","g",$6);else peerindex = "-"; + peerport = gensub(/^.*:([^:]*)$/,"\\1","g",$6); + stategroup = ""; + if(match($2, /^ESTAB$/))stategroup = "1-ESTABL"; + if(match($2, /^SYN-SENT$/))stategroup = "1-ESTABL"; + if(match($2, /^SYN-RECV$/))stategroup = "1-ESTABL"; + if(match($2, /^FIN-WAIT-1$/))stategroup = "2-CLOSIN"; + if(match($2, /^FIN-WAIT-2$/))stategroup = "2-CLOSIN"; + if(match($2, /^CLOSE-WAIT$/))stategroup = "2-CLOSIN"; + if(match($2, /^LAST-ACK$/))stategroup = "2-CLOSIN"; + if(match($2, /^CLOSING$/))stategroup = "2-CLOSIN"; + if(match($2, /^TIME-WAIT$/))stategroup = "3-WAITIN"; + if(match($2, /^LISTEN$/))stategroup = "0-LISTEN"; + if(match($2, /^UNCONN$/))stategroup = "0-LISTEN"; + if(stategroup =="")stategroup = "-"; + if($1 =="tcp" || $1 == "udp")print stategroup,$1,localip,localindex,localport,peerip,peerindex,peerport,$7,$8,$9; + }' \ + | sort -k1,1 -k3,3 -k4,4 -k6,6n \ + | awk 'BEGIN{print "StateGroup Protocol LocalAddr LocalIndex LocalPort PeerAddr PeerIndex PeerPort Process PID Cgroup"}1' \ + | column -t \ + | $_fzfCmd --multi --reverse --ansi --keep-right --tabstop 2 --header-lines 1 --prompt "List of all tcp/udp sockets > " --bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# better listing for listening tcp/udp sockets +sslisten() { + if [[ -f "/usr/bin/tmux" || -f "/usr/local/bin/tmux" ]]; then + local _fzfCmd="fzf-tmux" + else + local _fzfCmd="fzf" + fi + ss -Hlptun --cgroup 2>&1 \ + | awk '{gsub(/cgroup:/,"- - cgroup:",$7);if($7 =="")print $0" - - -";else print $0;}' \ + | awk '{ + gsub(/,fd=.+,pid=/,",",$7);gsub(/users:|\(|\)|,fd=[0-9]+/,"",$7);gsub(/,pid=/," ",$7); + gsub(/.+\/|cgroup:/,"",$8); + gsub(/.+\/|cgroup:/,"",$9); + if(match($5, /^.+%[^\]]+:.+$/))localip = gensub(/^(.*[0-9\]*])%.+:[0-9*]{1,5}$/,"\\1","g",$5);else localip = gensub(/^(.*):[0-9*]{1,5}$/,"\\1","g",$5); + if(match($5, /^.+%[^\]]+:.+$/))localindex = gensub(/^.+%([^\]]+):.+$/,"\\1","g",$5);else localindex = "-"; + localport = gensub(/^.*:([^:]*)$/,"\\1","g",$5); + if(match($6, /^.+%[^\]]+:.+$/))peerip = gensub(/^(.*[0-9\]*])%.+:[0-9*]{1,5}$/,"\\1","g",$6);else peerip = gensub(/^(.*):[0-9*]{1,5}$/,"\\1","g",$6); + if(match($6, /^.+%[^\]]+:.+$/))peerindex = gensub(/^.+%([^\]]+):.+$/,"\\1","g",$6);else peerindex = "-"; + peerport = gensub(/^.*:([^:]*)$/,"\\1","g",$6); + stategroup = ""; + if(match($2, /^LISTEN$/))stategroup = "LISTENING"; + if(match($2, /^UNCONN$/))stategroup = "LISTENING"; + if(stategroup =="")stategroup = "-"; + if($1 =="tcp" || $1 == "udp")print stategroup,$1,localip,localindex,localport,peerip,peerindex,peerport,$7,$8,$9; + }' \ + | sort -k1,1 -k3,3 -k4,4 -k6,6n \ + | awk 'BEGIN{print "StateGroup Protocol LocalAddr LocalIndex LocalPort PeerAddr PeerIndex PeerPort Process PID Cgroup"}1' \ + | column -t \ + | $_fzfCmd --multi --reverse --ansi --keep-right --tabstop 2 --header-lines 1 --prompt "List of LISTENING tcp/udp sockets > " --bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all +} + +# ============================== +# New commands or external programs +# ============================== + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# set shell working directory after leaving Vifm +if command -v vifmrun 1> /dev/null; then + vfcd() { + local dst="$(command vifmrun --choose-dir - "$@")" + if [ -z "$dst" ]; then + echo 'Directory picking cancelled/failed' + return 1 + fi + cd "$dst" + } +fi + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# simple archive extraction of all kinds, get dep with "sudo apt install tar bzip2 unrar gzip unzip 7zip xz-utils innoextract cabextract" +extract() { + + Help() { + cat <<- HEREDOC + + Archive extraction simplified. + Syntax: extract [-h] . + extract [path/file_name_2.ext] [path/file_name_3.ext] + options: + -h Print this Help. + + HEREDOC + } + while getopts ":h" option; do + case $option in + h) Help; return 0 ;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + + array=( tar unlzma bunzip2 unrar gunzip unzip uncompress 7z unxz innoextract cabextract ) + for cmd in "${array[@]}"; do + if [[ -z $(command -v "$cmd") ]]; then + echo "Requirements: at least $cmd could not be found:" + echo "sudo apt install tar bzip2 unrar gzip unzip 7zip xz-utils innoextract cabextract" + return 0 + fi + done + + for n in "$@" + do + if [[ -f "$n" ]] ; then + case "${n%,}" in + *.tar.bz2|*.tar.gz|*.tar.xz|*.tbz2|*.tgz|*.txz|*.tar) + tar xvf "$n" ;; + *.lzma) unlzma ./"$n" ;; + *.bz2) bunzip2 ./"$n" ;; + *.rar) unrar x -ad ./"$n" ;; + *.gz) gunzip ./"$n" ;; + *.zip) unzip ./"$n" ;; + *.z) uncompress ./"$n" ;; + *.7z|*.arj|*.cab|*.chm|*.deb|*.dmg|*.iso|*.lzh|*.msi|*.rpm|*.udf|*.wim|*.xar) + 7z x ./"$n" ;; + *.xz) unxz ./"$n" ;; + *gog*.exe|*gog*.sh) + innoextract -es -c1 -p1 ./"$n" ;; + *.exe) cabextract ./"$n" ;; + *) echo "extract: '$n' - unknown archive method"; return 1;; + esac + else + echo "'$n' - file does not exist" + return 1 + fi + done +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Command execution notifier +boop() { + + Help() { + cat <<- HEREDOC + + Command execution notifier. + - In front of a command: notification + sound + NTFY (env: NTFYBOOPSURL, NTFYBOOPSTOKEN) + (It send the command name and 1st arg, exec time and exit code) + - At the end of a chain: notification + sound + + Syntax: Pre command: boop [-h] [sudo] | boop ; boop + Post command: ; boop + options: + -h Print this Help. + + HEREDOC + } + while getopts ":h" option; do + case $option in + h) Help; return 0 ;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + + local STATUS="$?" + local ARRAY1=( /usr/bin/time play dunstify ) + local ARRAY2=( /usr/bin/time curl ) + for cmd in "${ARRAY2[@]}"; do + if [[ -z $(command -v "$cmd") ]]; then + echo "boop function requirements: command $cmd could not be found" + return 1 + fi + done + if [[ -z "$1" ]]; then + for cmd in "${ARRAY1[@]}"; do + if [[ -z $(command -v "$cmd") ]]; then + echo "boop function requirements: command $cmd could not be found" + return "$STATUS" + fi + done + if [[ "$STATUS" == '0' ]]; then + dunstify -a NTFY -u normal -i dialog-information "Commande terminée avec SUCCÉE ✅ !" + play -q -n synth pl G2 pl B2 pl D3 pl B3 pl D4 pl D4 delay 0 .05 .1 .15 .2 .25 remix - fade 0 2 .1 norm -1 + else + dunstify -a NTFY -u critical -i dialog-error "Commande terminée avec une ERREUR ⚠️ !" "Exit code : $STATUS" + play -q -n synth 3 sin 960 synth 3 sin fmod 1920 fade l 0 3 2.8 trim 0 1 repeat 2 norm -1 + fi + return "$STATUS" + else + local FILE=$(mktemp --suffix ".time") + boop2ntfy() { + local TIME=$(awk 'NR==1{print $1}' "$FILE") + local STATUS=$(awk 'NR==1{print $2}' "$FILE") + if [[ -z $(awk 'NR==1{print $4}' "$FILE") ]]; then + local CMDBEGIN=$(awk 'NR==1{print $3}' "$FILE") + else + local CMDBEGIN="$(awk 'NR==1{print $3}' "$FILE") $(awk 'NR==1{print $4}' "$FILE")" + fi + local HOSTNAME=$(hostname) + local URL="$NTFYBOOPSURL" + local TOKEN="$NTFYBOOPSTOKEN" + if [[ "$STATUS" == '0' ]]; then + if command -v dunstify 1> /dev/null; then + dunstify -a NTFY -u normal -i dialog-information "Commande terminée avec SUCCÉE ✅ !" "Durée : $TIME - Commande : $CMDBEGIN" + fi + if command -v play 1> /dev/null; then + play -q -n synth pl G2 pl B2 pl D3 pl B3 pl D4 pl D4 delay 0 .05 .1 .15 .2 .25 remix - fade 0 2 .1 norm -1 + fi + curl --max-time 25 -s -S -H "Authorization: Bearer ${TOKEN}" -H "X-Title: Hôte : $HOSTNAME" -H tag:tada -d "La commande \"$CMDBEGIN\" s'est terminée avec Succès ! (Durée : $TIME)" "$URL" &> /dev/null + else + if command -v dunstify 1> /dev/null; then + dunstify -a NTFY -u critical -i dialog-error "Commande terminée avec une ERREUR ⚠️ !" "Durée : $TIME - Exit code : $STATUS - Commande : $CMDBEGIN" + fi + if command -v play 1> /dev/null; then + play -q -n synth 3 sin 960 synth 3 sin fmod 1920 fade l 0 3 2.8 trim 0 1 repeat 2 norm -1 + fi + curl --max-time 25 -s -S -H "Authorization: Bearer ${TOKEN}" -H "X-Title: Hôte : $HOSTNAME" -H "X-Priority: 4" -H tag:warning -d "La commande \"$CMDBEGIN\" s'est interrompue avec le code d'Erreur $STATUS ! (Durée : $TIME)" "$URL" &> /dev/null + fi + } + /usr/bin/time -q -f "%E %x %C" -o "$FILE" "$@"; boop2ntfy + local EXIT=$(awk 'NR==1{print $2}' "$FILE") + rm -f "$FILE" + return "$EXIT" + fi +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# download a file from Google Drive with continuation : gdrive-dl ID FILENAME +gdrivedl() { + + Help() { + cat <<- HEREDOC + + Download a file from Google Drive with continuation. + Syntax: gdrive-dl [-h] + options: + -h Print this Help. + + HEREDOC + } + while getopts ":h" option; do + case $option in + h) Help; return 0 ;; + \?) echo -e "Unknown option: -$OPTARG \n" >&2; Help; return 1;; + : ) echo -e "Missing argument for -$OPTARG \n" >&2; Help; return 1;; + * ) echo -e "Unimplemented option: -$option \n" >&2; Help; return 1;; + esac + done + + local _ID=$1 + local _FILENAME=$2 + wget --prefer-family=IPv4 --force-directories --no-check-certificate --no-hsts --continue --load-cookies "/tmp/gdrive-cookies-${_ID}.txt" \ + "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/gdrive-cookies-${_ID}.txt \ + --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=${_ID}' -O- | \ + sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=${_ID}" -O "${XDG_DOWNLOAD_DIR}/$_FILENAME" \ + && rm -rf "/tmp/gdrive-cookies-${_ID}.txt" +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# mans - print brief help about a single option or command +mans() { + Help() { + cat <<- HEREDOC 1>&2 + + Mans print brief help about a single option or command + + Usage: mans [] + Example: + mans bash getopts Documentation for bash getopts + mans ssh -v Documentation for ssh -v flag + mans select SYNOPSIS for select(2) + mans 'open(2)' SYNOPSIS for open(2) + + HEREDOC + } + + if test $# -lt 1; then + Help + return 0 + fi + + manpage="$1" + # show the SYNOPSIS section if no section or option was given + option="${2:-SYNOPSIS}" + + # handle manpage(number) + case $manpage in *\(*\)) + page=${manpage%\(*\)} + section=${manpage#"$page"} + section=${section#\(} + section=${section%\)} + manpage="$page" + ;; + esac + + man ${section:+-s $section} "$manpage" | perl -n -e \ + 'BEGIN { + $option = "'"$option"'"; + $inside_option = 0; + } + if (!$inside_option) { + if (/^(\s*)\Q$option\E\b/p) { + # start of this option + $option_indentation = $1; + $inside_option = 1; + $saw_blank_line = 0; + print; + } + } else { + if (/^$/) { + $saw_blank_line = 1; + print; + } elsif (/^\Q$option_indentation\E\S/ and $saw_blank_line) { + # item at same indentation => start of next option + $inside_option = 0; + } elsif (/^\S/) { + # new heading => start of next section + $inside_option = 0; + } else { + print; + } + }' | { if command -v hl 1> /dev/null; then hlauto --man; else cat -; fi } +} + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# br - This function starts broot and executes the command +if command -v br 1> /dev/null; then + br() { + local cmd cmd_file code + cmd_file=$(mktemp) + if broot --outcmd "$cmd_file" "$@"; then + cmd=$(<"$cmd_file") + command rm -f "$cmd_file" + eval "$cmd" + else + code=$? + command rm -f "$cmd_file" + return "$code" + fi + } +fi diff --git a/.bashrc.d/12-bindings.bashrc b/.bashrc.d/12-bindings.bashrc new file mode 100644 index 0000000..d2ded6d --- /dev/null +++ b/.bashrc.d/12-bindings.bashrc @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +# ============================== +# BINDINGS +# ============================== + +# ============================== +# FZF-EMOJI-WIDGET +# ============================== + +# custom emojis selector with fzf, inspired by fzf shell bindings +# Require bash > v4; I did not copy other shells solutions from fzf shell bindings +# => pip --user install emoji-fzf +# => sudo apt install fzf +if [[ -f "$HOME/.local/bin/emoji-fzf" ]]; then + fzf-emoji-widget() { + Help() { + echo "FZF widget to insert or replace some emojis." + echo "Use it with bash>4 and 'bind -x'" + echo "Default: insert emojis in place." + echo + echo "Syntax: [-h] [-i]" + echo "options:" + echo "-h Print this Help." + echo "-i Replace the word before cursor, using it as a query" + echo + # READLINE(command line string) and READLINE_POINT(cursor position) + # are generated by bind. + } + local OPTIND=0 + while getopts ":hi" option; do + case $option in + h) Help + return 0 ;; + i) local _REPLACE=1 + local _QUERYLINE=${READLINE_LINE:0:READLINE_POINT} + local _QUERYLINE_POINT=${#_QUERYLINE} + local _QUERY="${_QUERYLINE/* /}" + local _QUERYLINE_START=$(( _QUERYLINE_POINT - "${#_QUERY}" )) ;; + \?) echo "Error: Invalid option: -$OPTARG" + Help + return 0 ;; + esac + done + shift $((OPTIND-1)) + + local selected=$(emoji-fzf preview --prepend | fzf-tmux -p 85% --multi --reverse --no-hscroll --header "Select multiple emojis with Tab and validate with Enter." --query "$_QUERY" | cut -d " " -f 2 | emoji-fzf get | xargs -d "\n") + + if [[ $_REPLACE == 1 ]]; then + READLINE_LINE="${READLINE_LINE:0:$_QUERYLINE_START}$selected${READLINE_LINE:READLINE_POINT}" + READLINE_POINT=$(( _QUERYLINE_START + "${#selected}" )) + else + READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:READLINE_POINT}" + READLINE_POINT=$(( READLINE_POINT + "${#selected}" )) + fi + } + + # ALT-e - Paste emojis from a list into the command line + bind -m emacs-standard -x '"\ee": fzf-emoji-widget' + bind -m vi-command -x '"\ee": fzf-emoji-widget' + bind -m vi-insert -x '"\ee": fzf-emoji-widget' + + # ALT-i - Replace the word before cursor with emojis from a list + bind -m emacs-standard -x '"\ei": fzf-emoji-widget -i' + bind -m vi-command -x '"\ei": fzf-emoji-widget -i' + bind -m vi-insert -x '"\ei": fzf-emoji-widget -i' +fi diff --git a/.bashrc.d/20-prompt-conf.bashrc b/.bashrc.d/20-prompt-conf.bashrc new file mode 100644 index 0000000..b4ffadc --- /dev/null +++ b/.bashrc.d/20-prompt-conf.bashrc @@ -0,0 +1,243 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +#################################### +# LIQUID PROMPT CONFIGURATION FILE # +#################################### + +# This example config file does not contain all possible config options, nor +# does it have detailed option descriptions. See the documentation for that: +# https://liquidprompt.readthedocs.io/en/stable/config.html + +############# +# BEHAVIOUR # +############# + +# Display the battery level in more urgent color when the level is below this threshold. +# Recommended value is 75 +LP_BATTERY_THRESHOLD=75 + +# Display the load average over the past minute when above this threshold. +# This value is scaled per CPU, so on a quad-core machine, the load average +# would need to be 2.40 or greater to be displayed. +# Recommended value is 0.60 +LP_LOAD_THRESHOLD=0.80 + +# Display the temperature when the temperate is above this threshold (in +# degrees Celsius). +# Recommended value is 60 +LP_TEMP_THRESHOLD=60 + +# Use the shorten path feature if the path is too long to fit in the prompt +# line. +# Recommended value is 1 +LP_ENABLE_SHORTEN_PATH=1 + +# The maximum percentage of the screen width used to display the path before +# removing the center portion of the path and replacing with '...'. +# Recommended value is 35 +LP_PATH_LENGTH=35 + +# The number of directories (including '/') to keep at the beginning of a +# shortened path. +# Recommended value is 2 +LP_PATH_KEEP=3 + +# Determine if the hostname should always be displayed, even if not connecting +# through network. +# Defaults to 0 (do not display hostname when locally connected) +# set to 1 if you want to always see the hostname +# set to -1 if you want to never see the hostname +LP_HOSTNAME_ALWAYS=0 + +# When to display the user name: +# 1: always display the user name +# 0: hide the logged user (always display different users) +# -1: never display the user name +# Default value is 1 +LP_USER_ALWAYS=1 + +# Display the actual values of load/batteries along with their +# corresponding marks. Set to 0 to only print the colored marks. +# Defaults to 1 (display percentages) +LP_PERCENTS_ALWAYS=1 + +# Display a user-defined set of environment variables. +# May show if the variables are unset, set, or their actual content +# (see below to configure which variables to watch). +LP_ENABLE_ENV_VARS=1 + +# The set of environment variables that the user wants to watch. +# Items should be a string with three space-separated elements +# of the form `" [ ]"` +# The string used when the variable is set may contain the `%s` mark, +# which is replaced by the actual content of the variable. +LP_ENV_VARS=( +# # Display "V" if VERBOSE is set, nothing if it's unset. +# "VERBOSE V" +# # Display the name of the desktop session, if set, T if unset. +# "DESKTOP_SESSION %s T" +# # Display "ed:" followed the name of the default editor, nothing if unset. +# "EDITOR ed:%s" +) + +# Use the permissions feature and display a red ':' before the prompt to show +# when you don't have write permission to the current directory. +# Recommended value is 1 +LP_ENABLE_PERM=1 + +# Enable the proxy detection feature. +# Recommended value is 1 +LP_ENABLE_PROXY=0 + +# Enable the jobs feature. +# Recommended value is 1 +LP_ENABLE_JOBS=1 + +# Enable the detached sessions feature. +# Default value is 1 +LP_ENABLE_DETACHED_SESSIONS=0 + +# Enable the load feature. +# Recommended value is 1 +LP_ENABLE_LOAD=1 + +# Enable the battery feature. +# Recommended value is 1 +LP_ENABLE_BATT=0 + +# Enable the 'sudo credentials' feature. +# Be warned that this may pollute the syslog if you don't have sudo +# credentials, and the sysadmin might hate you. +LP_ENABLE_SUDO=0 + +# Enable the directory stack support. +LP_ENABLE_DIRSTACK=0 + +# Enable the VCS features with the root account. +# Recommended value is 0 +LP_ENABLE_VCS_ROOT=0 + +# Enable the Git special features. +# Recommended value is 1 +LP_ENABLE_GIT=0 + +# Enable the Subversion special features. +# Recommended value is 1 +LP_ENABLE_SVN=0 + +# Enable the Mercurial special features. +# Recommended value is 1 +LP_ENABLE_HG=0 + +# Enable the Fossil special features. +# Recommended value is 1 +LP_ENABLE_FOSSIL=0 + +# Enable the Bazaar special features. +# Recommended value is 1 +LP_ENABLE_BZR=0 + +# Show time of when the current prompt was displayed. +LP_ENABLE_TIME=1 + +# Show runtime of the previous command if over LP_RUNTIME_THRESHOLD +# Recommended value is 0 +LP_ENABLE_RUNTIME=1 + +# Minimal runtime (in seconds) before the runtime will be displayed +# Recommended value is 2 +LP_RUNTIME_THRESHOLD=3 + +# Ring the terminal bell if the runtime of the previous command exceeded +# LP_RUNTIME_BELL_THRESHOLD +# Recommended value is 0 +LP_ENABLE_RUNTIME_BELL=0 + +# Minimal runtime (in seconds) before the terminal bell will be rung. +# Recommended value is 10 +LP_RUNTIME_BELL_THRESHOLD=10 + +# Display the virtualenv that is currently activated, if any +# Recommended value is 1 +LP_ENABLE_VIRTUALENV=1 + +# Display the ruby virtual env that is currently activated, if any +# Recommended value is 1 +LP_ENABLE_RUBY_VENV=1 + +# If using RVM, personalize the rvm-prompt. +# see http://rvm.io/workflow/prompt for details. +# Warning, this variable must be a shell array. +LP_RUBY_RVM_PROMPT_OPTIONS=(i v g s) + +# Display the terraform workspace that is currently activated, if any +# Recommended value is 0 +LP_ENABLE_TERRAFORM=0 + +# Display the enabled software collections, if any +# Recommended value is 1 +LP_ENABLE_SCLS=0 + +# Show current Kubernetes kubectl context +LP_ENABLE_KUBECONTEXT=0 + +# Delimiter to shorten kubectl context by removing a suffix. +# E.g. when your context names are dev-cluster and test-cluster, set to "-" +# in order to output "dev" and "test" in prompt. +LP_DELIMITER_KUBECONTEXT_SUFFIX= + +# Delimiter to shorten kubectl context by removing a prefix. +# E.g. when your context names are like +# arn:aws:eks:$REGION:$ACCOUNT_ID:cluster/$CLUSTER_NAME, set to "/" +# in order to output "$CLUSTER_NAME" in prompt. +LP_DELIMITER_KUBECONTEXT_PREFIX= + +# Display the current active AWS_PROFILE, if any +# Recommended value is 1 +LP_ENABLE_AWS_PROFILE=0 + +# Show highest system temperature +LP_ENABLE_TEMP=1 + +# When showing the time, use an analog clock instead of numeric values. +# Recommended value is 0 +LP_TIME_ANALOG=0 + +# Use the prompt as the title of the terminal window +# Recommended value is 0 +LP_ENABLE_TITLE=0 + +# Enable Title for screen, byobu, and tmux +LP_ENABLE_SCREEN_TITLE=0 + +# Use different colors for the different hosts you SSH to +LP_ENABLE_SSH_COLORS=0 + +# Show the error code of the last command if it was not 0 +LP_ENABLE_ERROR=1 + +# Show the (guessed) error meaning after the error code. +LP_ENABLE_ERROR_MEANING=0 + +# Extends the list of guessed error meanings (may produce wrong meanings). +LP_ENABLE_ERROR_MEANING_EXTENDED=0 + +# Specify an array of absolute paths in which all vcs will be disabled. +# Ex: ("/root" "/home/me/large-remove-svn-repo") +LP_DISABLED_VCS_PATHS=() + +# Use a local liquidpromptrc if it exists. +# Can be helpful if you sync your primary config across machines, or if +# there's a system-wide config at /etc/liquidpromptrc from which you'd +# like to make only minor deviations. +#LOCAL_RCFILE=$HOME/.liquidpromptrc.local +#[ -f "$LOCAL_RCFILE" ] && source "$LOCAL_RCFILE" + +# Show the value of $SHLVL, which is the number of nested shells. For example, +# if one runs bash inside their shell, it will open a new shell inside their current shell, +# and this will display “2”. +LP_ENABLE_SHLVL=0 + + +# vim: set et sts=4 sw=4 tw=120 ft=sh: diff --git a/.bashrc.d/21-prompt-theme.bashrc b/.bashrc.d/21-prompt-theme.bashrc new file mode 100644 index 0000000..992ec55 --- /dev/null +++ b/.bashrc.d/21-prompt-theme.bashrc @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +#################################### +# LIQUID PROMPT DEFAULT THEME FILE # +#################################### + +# Special characters +# Be sure to use characters that exists in the font you use. You can use several +# characters at once. +# Below is an example of how to fallback to ASCII if the term is not Unicode-capable. +# Defaults to UTF-8 characters. +if [[ "$(locale -k LC_CTYPE | sed -n 's/^charmap="\(.*\)"/\1/p')" == *"UTF-8"* ]]; then + # If charset is UTF-8. + LP_MARK_BATTERY="⌁" # in front of the battery charge + LP_MARK_ADAPTER="⏚" # displayed when plugged + LP_MARK_LOAD="⌂" # in front of the load + LP_MARK_TEMP="θ" # in front of the temp + LP_MARK_PROXY="↥" # indicate a proxy in use + LP_MARK_HG="☿" # prompt mark in hg repositories + LP_MARK_SVN="‡" # prompt mark in svn repositories + LP_MARK_GIT="±" # prompt mark in git repositories + LP_MARK_FOSSIL="⌘" # prompt mark in fossil repositories + LP_MARK_DISABLED="⌀" # prompt mark in directory with disabled VCS info + LP_MARK_UNTRACKED="*" # if git has untracked files + LP_MARK_STASH="+" # if git has stashs + LP_MARK_SHORTEN_PATH=" … " # prompt mark in shortened paths + LP_MARK_PERM=": " # separator between host and path + LP_MARK_KUBECONTEXT="⎈" # Kubernetes context + LP_MARK_SHLVL="└" # number of nested shells +else + # If charset is anything else, fallback to ASCII chars + LP_MARK_BATTERY="b" + LP_MARK_ADAPTER="p" + LP_MARK_LOAD="c" + LP_MARK_TEMP="T" + LP_MARK_PROXY="^" + LP_MARK_HG="m" + LP_MARK_SVN="=" + LP_MARK_GIT="+" + LP_MARK_FOSSIL="f" + LP_MARK_DISABLED="!" + LP_MARK_UNTRACKED="*" + LP_MARK_STASH="+" + LP_MARK_SHORTEN_PATH=" ... " + LP_MARK_PERM=": " + LP_MARK_KUBECONTEXT="k8s:" +fi + +LP_MARK_BRACKET_OPEN="[" # open bracket +LP_MARK_BRACKET_CLOSE="]" # close bracket +#LP_MARK_PREFIX=$'\n' # newline prompt mark prefix +LP_MARK_PREFIX=" " # prompt mark prefix +LP_PS1_PREFIX="" +LP_PS1_POSTFIX="" + +# Colors +# Available colors are: +# BOLD, BLACK, BOLD_GRAY, WHITE, BOLD_WHITE, +# RED, BOLD_RED, WARN_RED, CRIT_RED, DANGER_RED, +# GREEN, BOLD_GREEN, YELLOW, BOLD_YELLOW, BLUE, +# BOLD_BLUE, PURPLE, PINK, CYAN, BOLD_CYAN +# Set to a null string "" if you do not want color. + +# Current working directory +LP_COLOR_PATH="$BOLD" # as normal user +LP_COLOR_PATH_ROOT="$BOLD_YELLOW" # as root + +# Color of the proxy mark +LP_COLOR_PROXY="$BOLD_BLUE" + +# Jobs count +LP_COLOR_JOB_D="$YELLOW" # Detached (aka screen sessions) +LP_COLOR_JOB_R="$BOLD_YELLOW" # Running (xterm &) +LP_COLOR_JOB_Z="$BOLD_YELLOW" # Sleeping (Ctrl-Z) + +# Last error code +LP_COLOR_ERR="$PURPLE" + +# Prompt mark +LP_COLOR_MARK="$BOLD" # as user +LP_COLOR_MARK_ROOT="$BOLD_RED" # as root +LP_COLOR_MARK_SUDO="$BOLD_RED" # when sudo credentials are cached + +# Current user +LP_COLOR_USER_LOGGED="" # user who logged in +LP_COLOR_USER_ALT="$BOLD" # user but not the one who logged in +LP_COLOR_USER_ROOT="$BOLD_YELLOW" # root + +# Hostname +LP_COLOR_HOST="" # local host +LP_COLOR_SSH="$BLUE" # connected via SSH +LP_COLOR_SU="$BOLD_YELLOW" # connected remotely but in new environment through su/sudo +LP_COLOR_TELNET="$WARN_RED" # connected via telnet +LP_COLOR_X11_ON="$GREEN" # connected with X11 support +LP_COLOR_X11_OFF="$YELLOW" # connected without X11 support + +# Separation mark (aka permission in the working dir) +LP_COLOR_WRITE="$GREEN" # have write permission +LP_COLOR_NOWRITE="$RED" # do not have write permission + +# VCS +LP_COLOR_UP="$GREEN" # repository is up to date / a push have been made +LP_COLOR_COMMITS="$YELLOW" # some commits have not been pushed +LP_COLOR_COMMITS_BEHIND="$BOLD_RED" # some commits have not been pushed +LP_COLOR_CHANGES="$RED" # there is some changes to commit +LP_COLOR_DIFF="$PURPLE" # number of lines impacted by current changes + +# Battery +LP_COLOR_CHARGING_ABOVE="$GREEN" # charging and above threshold +LP_COLOR_CHARGING_UNDER="$YELLOW" # charging but under threshold +LP_COLOR_DISCHARGING_ABOVE="$YELLOW" # discharging but above threshold +LP_COLOR_DISCHARGING_UNDER="$RED" # discharging and under threshold + +# Time +LP_COLOR_TIME="$BLUE" + +# Brackets inside screen/tmux +LP_COLOR_IN_MULTIPLEXER="$BOLD_BLUE" + +# Virtual environment +LP_COLOR_VIRTUALENV="$CYAN" + +# Kubernetes +LP_COLOR_KUBECONTEXT="$CYAN" + +# Terraform +LP_COLOR_TERRAFORM="$PINK" + +# Runtime +LP_COLOR_RUNTIME="$YELLOW" + +# Color map (for battery and load levels, and temperature) +# Range from 0 (nothing special) to 9 (alert) +LP_COLORMAP=( + "" + "$GREEN" + "$BOLD_GREEN" + "$YELLOW" + "$BOLD_YELLOW" + "$RED" + "$BOLD_RED" + "$WARN_RED" + "$CRIT_RED" + "$DANGER_RED" +) + +# vim: set et sts=4 sw=4 tw=120 ft=sh: diff --git a/.bashrc.d/22-prompt.bashrc b/.bashrc.d/22-prompt.bashrc new file mode 100644 index 0000000..8e82a19 --- /dev/null +++ b/.bashrc.d/22-prompt.bashrc @@ -0,0 +1,4627 @@ +#!/usr/bin/env bash +# executed by bash(1) for non-login shells. + +################################################################################ +# LIQUID PROMPT +# An intelligent and non-intrusive prompt for Bash and zsh +################################################################################ + +_LP_VERSION=(2 2 0 alpha 1) + +# Licensed under the AGPL version 3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# See the README.md file for a summary of features. + +# Issue #161: do not load if not an interactive shell +# Do not exit if '--no-activate' flag was passed, as it overrides this check +# shellcheck disable=SC2268 +[ "x${-##*i}" = "x$-" ] || [ -z "${TERM-}" ] || [ "x${TERM-}" = xdumb ] || [ "x${TERM-}" = xunknown ] && [ "x${1-}" != "x--no-activate" ] && return + +if test -n "${BASH_VERSION-}"; then + # Check for recent enough version of bash. + if (( ${BASH_VERSINFO[0]:-0} < 3 || ( ${BASH_VERSINFO[0]:-0} == 3 && ${BASH_VERSINFO[1]:-0} < 2 ) )); then + echo "liquidprompt: Bash version $BASH_VERSION not supported" 2>&1 + return + fi + + _LP_SHELL_bash=1 + _LP_SHELL_zsh=0 + _LP_OPEN_ESC="\[" + _LP_CLOSE_ESC="\]" + + _LP_MARK_SYMBOL='$' + + _LP_FIRST_INDEX=0 + _LP_PERCENT='%' # must be escaped on zsh + _LP_BACKSLASH='\\' # must be escaped on bash + + # Escape the given strings + # Must be used for all strings injected in PS1 that may comes from remote sources, + # like $PWD, VCS branch names... + __lp_escape() { + ret="${1//\\/\\\\}" + if shopt -q promptvars ; then + ret="${ret//\$/\\\$}" + ret="${ret//\`/\\\`}" + fi + } + + __lp_strip_escapes() { + if ! shopt -q extglob ; then + local _lp_no_extglob=true + shopt -s extglob + fi + + ret="${1//"${_LP_OPEN_ESC}"!(*"${_LP_CLOSE_ESC}"*)"${_LP_CLOSE_ESC}"}" + + if [[ -n ${_lp_no_extglob-} ]]; then + shopt -u extglob + fi + + ret="${ret//\\\\/\\}" + if shopt -q promptvars ; then + ret="${ret//\\\$/\$}" + ret="${ret//\\\`/\`}" + fi + } +elif test -n "${ZSH_VERSION-}" ; then + # Check for recent enough version of zsh. + if (( ${ZSH_VERSION:0:1} < 5 )); then + echo "liquidprompt: Zsh version $ZSH_VERSION not supported" 2>&1 + return + fi + + _LP_SHELL_bash=0 + _LP_SHELL_zsh=1 + _LP_OPEN_ESC="%{" + _LP_CLOSE_ESC="%}" + + _LP_MARK_SYMBOL='%%' + + _LP_FIRST_INDEX=1 + _LP_PERCENT='%%' + _LP_BACKSLASH='\' + + __lp_escape() { + ret="${1//\\/\\\\}" + ret="${ret//\%/%%}" + if [[ -o promptbang ]]; then + ret="${ret//!/!!}" + fi + if [[ -o promptsubst ]]; then + ret="${ret//\$/\\\$}" + ret="${ret//\`/\\\`}" + fi + } + + __lp_strip_escapes() { + # shellcheck disable=SC2296 + ret="${(S)1//"${_LP_OPEN_ESC}"*"${_LP_CLOSE_ESC}"}" + ret="${ret//\\\\/\\}" + ret="${ret//\%\%/%}" + if [[ -o promptbang ]]; then + ret="${ret//!!/!}" + fi + if [[ -o promptsubst ]]; then + ret="${ret//\\\$/\$}" + ret="${ret//\\\`/\`}" + fi + } +else + echo "liquidprompt: shell not supported" >&2 + return +fi + +__lp_use_bash_preexec() { + # If https://github.com/rcaloras/bash-preexec is present, we can (and should, because it interferes with + # PROMPT_COMMAND and DEBUG) use the zsh-hook like behavior it provides. + [[ "${__bp_imported-}" == "defined" ]] +} + +__lp_array_contains() { + local target="$1" + shift + + for element; do + if [[ $element == "$target" ]]; then + return 0 + fi + done + return 1 +} + +############### +# OS specific # +############### + +# LP_OS detection, default to Linux +case "$(uname)" in + FreeBSD) LP_OS=FreeBSD ;; + DragonFly) LP_OS=FreeBSD ;; + OpenBSD) LP_OS=OpenBSD ;; + Darwin) LP_OS=Darwin ;; + SunOS) LP_OS=SunOS ;; + *) LP_OS=Linux ;; +esac + +_lp_os() { + # Fine-grained OS detection: family, kernel, distrib, version. + (( LP_ENABLE_OS )) || return 2 + + # Output variables: + lp_os_arch= + lp_os_family= + lp_os_kernel= + lp_os_distrib= + lp_os_version= + + local uname_sm kernel + + # Get the kernel (and possibly the arch). + if (( LP_ENABLE_OS_ARCH )); then + # We can get the arch along, with a single subshell. + uname_sm="$(uname -s -m)" + IFS=' ' read -r kernel lp_os_arch <<<"$uname_sm" + else # In any case, we need to know the kernel. + kernel="$(uname -s)" + fi + + if (( LP_ENABLE_OS_KERNEL + || LP_ENABLE_OS_FAMILY + || LP_ENABLE_OS_DISTRIB + || LP_ENABLE_OS_VERSION )); then + + case "$kernel" in + FreeBSD) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=BSD; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=FreeBSD; fi + ;; + DragonFly) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=BSD; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=DragonFly; fi + ;; + OpenBSD) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=BSD; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=OpenBSD; fi + ;; + Darwin) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=BSD; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=Darwin; fi + ;; + SunOS) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=UNIX; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=SunOS; fi + ;; + CYGWIN*) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=Windows; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=Cygwin; fi + ;; + MSYS*) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=Windows; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=MSYS; fi + ;; + MINGW*) + if (( LP_ENABLE_OS_FAMILY )); then lp_os_family=Windows; fi + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=MinGW; fi + ;; + Linux) + if (( LP_ENABLE_OS_KERNEL )); then lp_os_kernel=Linux; fi + if (( LP_ENABLE_OS_FAMILY )); then + if [[ "$kernel" == "Linux" ]]; then + osfamily="$(uname -o)" + if [[ "$osfamily" == "Android" ]]; then + lp_os_family=Android + return + fi # Android + fi # Linux + lp_os_family=GNU + fi + # TODO parse HOME_URL and add an hyperlink. + if (( LP_ENABLE_OS_DISTRIB && LP_ENABLE_OS_VERSION )); then + if _lp_grep_fields "/etc/os-release" "=" "ID" "VERSION_CODENAME"; then + lp_os_distrib=${lp_grep_fields[_LP_FIRST_INDEX+0]-} + lp_os_version=${lp_grep_fields[_LP_FIRST_INDEX+1]-} + fi + elif (( LP_ENABLE_OS_DISTRIB )); then + if _lp_grep_fields "/etc/os-release" "=" "ID"; then + lp_os_distrib=${lp_grep_fields[_LP_FIRST_INDEX+0]-} + fi + elif (( LP_ENABLE_OS_VERSION )); then + if _lp_grep_fields "/etc/os-release" "=" "VERSION_CODENAME"; then + lp_os_version=${lp_grep_fields[_LP_FIRST_INDEX+0]-} + fi + fi + ;; + esac + fi + + if [[ -z "${lp_os_arch}${lp_os_family}${lp_os_kernel}${lp_os_distrib}${lp_os_version}" ]]; then + return 1 + fi +} + +_lp_os_color() { + # Substitute lp_os_* names with icons/colored strings (from LP_MARK_OS), + # or hash colored versions. + # Join everything with LP_MARK_OS_SEP. + + lp_os_color= + local lp_os_arch lp_os_family lp_os_kernel lp_os_distrib lp_os_version + + _lp_os || return "$?" + + local lp_substitute lp_hash_color lp_join + local active + active=() + for value in \ + "$lp_os_arch" "$lp_os_family" "$lp_os_kernel" "$lp_os_distrib" "$lp_os_version"; do + if [[ -n "$value" ]]; then + if _lp_substitute "$value" "${LP_MARK_OS[@]}"; then + active+=("$lp_substitute") + elif _lp_hash_color "$value"; then + active+=("$lp_hash_color") + else + active+=("$value") + fi + fi + done + + if [[ ${#active} -gt 0 ]]; then + _lp_join "$LP_MARK_OS_SEP" "${active[@]}" + lp_os_color="$lp_join" + else + return 1 + fi +} + +################# +# CONFIGURATION # +################# + +# Return true if the current Liquid Prompt version is greater than or equal to the specified version. +# [OPTIONAL?] Return 1 if minor or less version difference, 2 if major difference. +_lp_version_greatereq() { # major, minor, [patch], [string], [number] + local major="$1" minor="$2" patch="${3-}" string="${4-}" number="${5-}" + if (( major > _LP_VERSION[_LP_FIRST_INDEX+0] )); then + return 2 + elif (( major == _LP_VERSION[_LP_FIRST_INDEX+0] )); then + if (( minor > _LP_VERSION[_LP_FIRST_INDEX+1] )); then + return 1 + elif (( minor == _LP_VERSION[_LP_FIRST_INDEX+1] )) && [[ -n $patch ]]; then + if (( patch > _LP_VERSION[_LP_FIRST_INDEX+2] )); then + return 1 + elif (( patch == _LP_VERSION[_LP_FIRST_INDEX+2] )) && [[ -n $string && -n $number ]]; then + if [[ $string == "${_LP_VERSION[_LP_FIRST_INDEX+3]}" ]]; then + if (( number > _LP_VERSION[_LP_FIRST_INDEX+4] )); then + return 1 + fi + elif [[ ${_LP_VERSION[_LP_FIRST_INDEX+3]} == "alpha" || \ + ( ${_LP_VERSION[_LP_FIRST_INDEX+3]} == "beta" && \ + $string == "rc" ) ]]; then + return 1 + fi + fi + fi + fi + + return 0 +} + +# Load the user configuration and setup defaults. +# shellcheck disable=SC2034 +__lp_source_config() { + + local lp_terminal_format af_color='' ab_color='' + + # Colors: variables are local so they will have a value only + # during config loading and will not conflict with other values + # with the same names defined by the user outside the config. + local BOLD="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${_LP_CLOSE_ESC}" + + # Foreground colors + __lp_foreground_color 0 + local BLACK="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_GRAY="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + + __lp_foreground_color 1 + local RED="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_RED="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + __lp_foreground_color 0 + __lp_background_color 1 + local WARN_RED="${_LP_OPEN_ESC}${af_color}${ab_color}${_LP_CLOSE_ESC}" + __lp_foreground_color 7 + local CRIT_RED="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${ab_color}${_LP_CLOSE_ESC}" + __lp_foreground_color 3 + local DANGER_RED="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${ab_color}${_LP_CLOSE_ESC}" + + __lp_foreground_color 2 + local GREEN="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_GREEN="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + + __lp_foreground_color 3 + local YELLOW="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_YELLOW="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + + __lp_foreground_color 4 + local BLUE="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_BLUE="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + + __lp_foreground_color 5 + local PURPLE="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local MAGENTA="${PURPLE}" + local PINK="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + local BOLD_PURPLE="${PINK}" + local BOLD_MAGENTA="${PINK}" + + __lp_foreground_color 6 + local CYAN="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_CYAN="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + + __lp_foreground_color 7 + local WHITE="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + local BOLD_WHITE="${_LP_OPEN_ESC}${_LP_TI_BOLD-}${af_color}${_LP_CLOSE_ESC}" + + # NO_COL is special: it will be used at runtime, not just during config loading + NO_COL="${_LP_OPEN_ESC}${_LP_TI_RESET-}${_LP_CLOSE_ESC}" + + # compute the hash of the hostname and get the corresponding number in + # [1-6] (red,green,yellow,blue,purple or cyan) + local lp_hostname_hash + __lp_hostname_hash + __lp_foreground_color "$(( 1 + lp_hostname_hash % 6 ))" + LP_COLOR_HOST_HASH="${_LP_OPEN_ESC}${af_color}${_LP_CLOSE_ESC}" + + + # Default values (globals) + LP_TIME_FORMAT=${LP_TIME_FORMAT:-"%H:%M:%S"} + LP_BATTERY_THRESHOLD=${LP_BATTERY_THRESHOLD:-75} + LP_LOAD_THRESHOLD=${LP_LOAD_THRESHOLD:-0.60} + LP_LOAD_CAP=${LP_LOAD_CAP:-2.0} + LP_TEMP_THRESHOLD=${LP_TEMP_THRESHOLD:-60} + LP_WIFI_STRENGTH_THRESHOLD=${LP_WIFI_STRENGTH_THRESHOLD:-40} + LP_RUNTIME_THRESHOLD=${LP_RUNTIME_THRESHOLD:-2} + LP_RUNTIME_BELL_THRESHOLD=${LP_RUNTIME_BELL_THRESHOLD:-10} + LP_PATH_LENGTH=${LP_PATH_LENGTH:-35} + LP_PATH_KEEP=${LP_PATH_KEEP:-2} + LP_PATH_CHARACTER_KEEP=${LP_PATH_CHARACTER_KEEP:-3} + LP_PATH_METHOD=${LP_PATH_METHOD:-truncate_chars_from_path_left} + LP_PATH_VCS_ROOT=${LP_PATH_VCS_ROOT:-1} + LP_HOSTNAME_ALWAYS=${LP_HOSTNAME_ALWAYS:-0} + LP_HOSTNAME_METHOD=${LP_HOSTNAME_METHOD:-short} + LP_USER_ALWAYS=${LP_USER_ALWAYS:-1} + LP_PERCENTS_ALWAYS=${LP_PERCENTS_ALWAYS:-1} + LP_PS1=${LP_PS1:-""} + LP_PS1_PREFIX=${LP_PS1_PREFIX:-""} + LP_PS1_POSTFIX=${LP_PS1_POSTFIX:-""} + LP_DELIMITER_KUBECONTEXT_SUFFIX=${LP_DELIMITER_KUBECONTEXT_SUFFIX:-""} + LP_DELIMITER_KUBECONTEXT_PREFIX=${LP_DELIMITER_KUBECONTEXT_PREFIX:-""} + LP_ENV_VARS=( ${LP_ENV_VARS[@]+"${LP_ENV_VARS[@]}"} ) + + LP_ENABLE_PERM=${LP_ENABLE_PERM:-1} + LP_ENABLE_SHORTEN_PATH=${LP_ENABLE_SHORTEN_PATH:-1} + LP_ENABLE_PROXY=${LP_ENABLE_PROXY:-1} + LP_ENABLE_ENV_VARS=${LP_ENABLE_ENV_VARS:-1} + LP_ENABLE_TEMP=${LP_ENABLE_TEMP:-1} + LP_ENABLE_JOBS=${LP_ENABLE_JOBS:-1} + LP_ENABLE_DETACHED_SESSIONS=${LP_ENABLE_DETACHED_SESSIONS:-1} + LP_ENABLE_LOAD=${LP_ENABLE_LOAD:-1} + LP_ENABLE_BATT=${LP_ENABLE_BATT:-1} + LP_ENABLE_GIT=${LP_ENABLE_GIT:-1} + LP_ENABLE_SVN=${LP_ENABLE_SVN:-1} + LP_ENABLE_FOSSIL=${LP_ENABLE_FOSSIL:-1} + LP_ENABLE_HG=${LP_ENABLE_HG:-1} + LP_HG_COMMAND=${LP_HG_COMMAND:-hg} + LP_ENABLE_BZR=${LP_ENABLE_BZR:-1} + LP_ENABLE_TIME=${LP_ENABLE_TIME:-0} + LP_TIME_ANALOG=${LP_TIME_ANALOG:-0} + LP_ENABLE_RUNTIME=${LP_ENABLE_RUNTIME:-1} + LP_ENABLE_RUNTIME_BELL=${LP_ENABLE_RUNTIME_BELL:-0} + LP_ENABLE_VIRTUALENV=${LP_ENABLE_VIRTUALENV:-1} + LP_ENABLE_NODE_VENV=${LP_ENABLE_NODE_VENV:-0} + LP_ENABLE_RUBY_VENV=${LP_ENABLE_RUBY_VENV:-1} + LP_RUBY_RVM_PROMPT_OPTIONS=( ${LP_RUBY_RVM_PROMPT_OPTIONS[@]+"${LP_RUBY_RVM_PROMPT_OPTIONS[@]}"} ) + [[ ${#LP_RUBY_RVM_PROMPT_OPTIONS[@]} == 0 ]] && LP_RUBY_RVM_PROMPT_OPTIONS=(i v g s) + LP_ENABLE_TERRAFORM=${LP_ENABLE_TERRAFORM:-0} + LP_ENABLE_CONTAINER=${LP_ENABLE_CONTAINER:-0} + LP_ENABLE_SCLS=${LP_ENABLE_SCLS:-1} + LP_ENABLE_AWS_PROFILE=${LP_ENABLE_AWS_PROFILE:-1} + LP_ENABLE_MODULES=${LP_ENABLE_MODULES:-1} + LP_ENABLE_MODULES_VERSIONS=${LP_ENABLE_MODULES_VERSIONS:-1} + LP_ENABLE_MODULES_HASHCOLOR=${LP_ENABLE_MODULES_HASHCOLOR:-0} + LP_ENABLE_VCS_ROOT=${LP_ENABLE_VCS_ROOT:-0} + LP_ENABLE_TITLE=${LP_ENABLE_TITLE:-0} + LP_ENABLE_SCREEN_TITLE=${LP_ENABLE_SCREEN_TITLE:-0} + LP_ENABLE_TITLE_COMMAND=${LP_ENABLE_TITLE_COMMAND:-1} + LP_ENABLE_SSH_COLORS=${LP_ENABLE_SSH_COLORS:-0} + LP_DISABLED_VCS_PATHS=( ${LP_DISABLED_VCS_PATHS[@]+"${LP_DISABLED_VCS_PATHS[@]}"} ) + LP_ENABLE_SUDO=${LP_ENABLE_SUDO:-0} + LP_ENABLE_COLOR=${LP_ENABLE_COLOR:-1} + LP_ENABLE_ERROR=${LP_ENABLE_ERROR:-1} + LP_ENABLE_ERROR_MEANING=${LP_ENABLE_ERROR_MEANING:-0} + LP_ENABLE_ERROR_MEANING_EXTENDED=${LP_ENABLE_ERROR_MEANING_EXTENDED:-0} + LP_ENABLE_DIRSTACK=${LP_ENABLE_DIRSTACK:-0} + LP_ENABLE_KUBECONTEXT=${LP_ENABLE_KUBECONTEXT:-0} + LP_ENABLE_KUBE_NAMESPACE=${LP_ENABLE_KUBE_NAMESPACE:-0} + LP_ENABLE_CMAKE=${LP_ENABLE_CMAKE:-0} + LP_ENABLE_SHLVL=${LP_ENABLE_SHLVL:-1} + LP_ENABLE_WIFI_STRENGTH=${LP_ENABLE_WIFI_STRENGTH:-0} + LP_ENABLE_OS=${LP_ENABLE_OS:-0} + LP_ENABLE_OS_ARCH=${LP_ENABLE_OS_ARCH:-0} + LP_ENABLE_OS_FAMILY=${LP_ENABLE_OS_FAMILY:-0} + LP_ENABLE_OS_KERNEL=${LP_ENABLE_OS_KERNEL:-1} + LP_ENABLE_OS_DISTRIB=${LP_ENABLE_OS_DISTRIB:-0} + LP_ENABLE_OS_VERSION=${LP_ENABLE_OS_VERSION:-1} + LP_ENABLE_HYPERLINKS=${LP_ENABLE_HYPERLINKS:-0} + + LP_MARK_DEFAULT="${LP_MARK_DEFAULT:-$_LP_MARK_SYMBOL}" + LP_MARK_BATTERY="${LP_MARK_BATTERY:-"⌁"}" + LP_MARK_ADAPTER="${LP_MARK_ADAPTER:-"⏚"}" + LP_MARK_LOAD="${LP_MARK_LOAD:-"⌂"}" + LP_MARK_TEMP="${LP_MARK_TEMP:-"θ"}" + LP_MARK_PROXY="${LP_MARK_PROXY:-"↥"}" + LP_MARK_ENV_VARS_OPEN="${LP_MARK_ENV_VARS_OPEN:-"("}" + LP_MARK_ENV_VARS_SEP="${LP_MARK_ENV_VARS_SEP:-" "}" + LP_MARK_ENV_VARS_CLOSE="${LP_MARK_ENV_VARS_CLOSE:-")"}" + LP_MARK_HG="${LP_MARK_HG:-"☿"}" + LP_MARK_SVN="${LP_MARK_SVN:-"‡"}" + LP_MARK_GIT="${LP_MARK_GIT:-"±"}" + LP_MARK_VCSH="${LP_MARK_VCSH:-"|"}" + LP_MARK_FOSSIL="${LP_MARK_FOSSIL:-"⌘"}" + LP_MARK_BZR="${LP_MARK_BZR:-"⚯"}" + LP_MARK_DISABLED="${LP_MARK_DISABLED:-"⌀"}" + LP_MARK_UNTRACKED="${LP_MARK_UNTRACKED:-"*"}" + LP_MARK_STASH="${LP_MARK_STASH:-"+"}" + LP_MARK_BRACKET_OPEN="${LP_MARK_BRACKET_OPEN:-"["}" + LP_MARK_BRACKET_CLOSE="${LP_MARK_BRACKET_CLOSE:-"]"}" + LP_MARK_MULTIPLEXER_OPEN="${LP_MARK_MULTIPLEXER_OPEN:-"$LP_MARK_BRACKET_OPEN"}" + LP_MARK_MULTIPLEXER_CLOSE="${LP_MARK_MULTIPLEXER_CLOSE:-"$LP_MARK_BRACKET_CLOSE"}" + LP_MARK_SHORTEN_PATH="${LP_MARK_SHORTEN_PATH:-" … "}" + LP_MARK_PREFIX="${LP_MARK_PREFIX:-" "}" + LP_MARK_PERM="${LP_MARK_PERM:-":"}" + LP_MARK_DIRSTACK="${LP_MARK_DIRSTACK:-"⚞"}" + LP_MARK_SHLVL="${LP_MARK_SHLVL:-"└"}" + LP_MARK_WIFI="${LP_MARK_WIFI:-"📶"}" + LP_MARK_DEV_OPEN="${LP_MARK_DEV_OPEN:-"<"}" + LP_MARK_DEV_CLOSE="${LP_MARK_DEV_CLOSE:-">"}" + LP_MARK_DEV_MID="${LP_MARK_DEV_MID:-"|"}" + LP_MARK_MODULES_OPEN="${LP_MARK_MODULES_OPEN:-""}" + LP_MARK_MODULES_SEP="${LP_MARK_MODULES_SEP:-":"}" + LP_MARK_MODULES_CLOSE="${LP_MARK_MODULES_CLOSE:-""}" + LP_MARK_CMAKE="${LP_MARK_CMAKE:-":"}" + LP_MARK_KUBECONTEXT=${LP_MARK_KUBECONTEXT:-"⎈"} + LP_MARK_JOBS_SEPARATOR="${LP_MARK_JOBS_SEPARATOR:-"/"}" + LP_MARK_OS_SEP=${LP_MARK_OS_SEP:-"/"} + LP_MARK_OS=( ${LP_MARK_OS[@]+"${LP_MARK_OS[@]}"} ) + + LP_COLOR_CMAKE_DEBUG=${LP_COLOR_CMAKE_DEBUG:-$MAGENTA} + LP_COLOR_CMAKE_RWDI=${LP_COLOR_CMAKE_RWDI:-$BLUE} + LP_COLOR_CMAKE_RELEASE=${LP_COLOR_CMAKE_RELEASE:-$CYAN} + LP_COLOR_PATH=${LP_COLOR_PATH:-$NO_COL} + lp_terminal_format 8 -1 0 0 -1 + LP_COLOR_PATH_SEPARATOR=${LP_COLOR_PATH_SEPARATOR:-$lp_terminal_format} + LP_COLOR_PATH_SHORTENED=${LP_COLOR_PATH_SHORTENED:-$lp_terminal_format} + lp_terminal_format -1 -1 1 0 + LP_COLOR_PATH_VCS_ROOT=${LP_COLOR_PATH_VCS_ROOT:-$lp_terminal_format} + LP_COLOR_PATH_LAST_DIR=${LP_COLOR_PATH_LAST_DIR:-$lp_terminal_format} + LP_COLOR_PATH_ROOT=${LP_COLOR_PATH_ROOT:-$BOLD_YELLOW} + LP_COLOR_PROXY=${LP_COLOR_PROXY:-$BOLD_BLUE} + LP_COLOR_ENV_VARS_UNSET=${LP_COLOR_ENV_VARS_UNSET:-$BLUE} + LP_COLOR_ENV_VARS_SET=${LP_COLOR_ENV_VARS_SET:-$BOLD_BLUE} + LP_COLOR_JOB_D=${LP_COLOR_JOB_D:-$YELLOW} + LP_COLOR_JOB_R=${LP_COLOR_JOB_R:-$BOLD_YELLOW} + LP_COLOR_JOB_Z=${LP_COLOR_JOB_Z:-$BOLD_YELLOW} + LP_COLOR_ERR=${LP_COLOR_ERR:-$PURPLE} + LP_COLOR_ERR_MEANING=${LP_COLOR_ERR_MEANING:-$LP_COLOR_ERR} + LP_COLOR_MARK=${LP_COLOR_MARK:-$BOLD} + LP_COLOR_MARK_ROOT=${LP_COLOR_MARK_ROOT:-$BOLD_RED} + LP_COLOR_MARK_SUDO=${LP_COLOR_MARK_SUDO:-$LP_COLOR_MARK_ROOT} + LP_COLOR_USER_LOGGED=${LP_COLOR_USER_LOGGED:-""} + LP_COLOR_USER_ALT=${LP_COLOR_USER_ALT:-$BOLD} + LP_COLOR_USER_ROOT=${LP_COLOR_USER_ROOT:-$BOLD_YELLOW} + LP_COLOR_HOST=${LP_COLOR_HOST:-""} + LP_COLOR_SSH=${LP_COLOR_SSH:-$BLUE} + LP_COLOR_SU=${LP_COLOR_SU:-$BOLD_YELLOW} + LP_COLOR_TELNET=${LP_COLOR_TELNET:-$WARN_RED} + LP_COLOR_X11_ON=${LP_COLOR_X11_ON:-$GREEN} + LP_COLOR_X11_OFF=${LP_COLOR_X11_OFF:-$YELLOW} + LP_COLOR_WRITE=${LP_COLOR_WRITE:-$GREEN} + LP_COLOR_NOWRITE=${LP_COLOR_NOWRITE:-$RED} + LP_COLOR_UP=${LP_COLOR_UP:-$GREEN} + LP_COLOR_COMMITS=${LP_COLOR_COMMITS:-$YELLOW} + LP_COLOR_COMMITS_BEHIND=${LP_COLOR_COMMITS_BEHIND:-$BOLD_RED} + LP_COLOR_CHANGES=${LP_COLOR_CHANGES:-$RED} + LP_COLOR_DIFF=${LP_COLOR_DIFF:-$PURPLE} + LP_COLOR_CHARGING_ABOVE=${LP_COLOR_CHARGING_ABOVE:-$GREEN} + LP_COLOR_CHARGING_UNDER=${LP_COLOR_CHARGING_UNDER:-$YELLOW} + LP_COLOR_DISCHARGING_ABOVE=${LP_COLOR_DISCHARGING_ABOVE:-$YELLOW} + LP_COLOR_DISCHARGING_UNDER=${LP_COLOR_DISCHARGING_UNDER:-$RED} + LP_COLOR_TIME=${LP_COLOR_TIME:-$BLUE} + LP_COLOR_IN_MULTIPLEXER=${LP_COLOR_IN_MULTIPLEXER:-$BOLD_BLUE} + LP_COLOR_RUNTIME=${LP_COLOR_RUNTIME:-$YELLOW} + LP_COLOR_VIRTUALENV=${LP_COLOR_VIRTUALENV:-$CYAN} + LP_COLOR_NODE_VENV=${LP_COLOR_NODE_VENV:-$LP_COLOR_VIRTUALENV} + LP_COLOR_RUBY_VENV=${LP_COLOR_RUBY_VENV:-$LP_COLOR_VIRTUALENV} + LP_COLOR_TERRAFORM=${LP_COLOR_TERRAFORM:-$PINK} + LP_COLOR_CONTAINER=${LP_COLOR_CONTAINER:-$BOLD_BLUE} + LP_COLOR_DIRSTACK=${LP_COLOR_DIRSTACK:-$BOLD_YELLOW} + LP_COLOR_KUBECONTEXT=${LP_COLOR_KUBECONTEXT:-$CYAN} + LP_COLOR_AWS_PROFILE=${LP_COLOR_AWS_PROFILE:-$YELLOW} + LP_COLOR_MODULES=${LP_COLOR_MODULES:-$BLUE} + LP_COLOR_SHLVL=${LP_COLOR_SHLVL:-$BOLD_GREEN} + + LP_COLORMAP=( ${LP_COLORMAP[@]+"${LP_COLORMAP[@]}"} ) + if [[ ${#LP_COLORMAP[@]} == 0 ]]; then + LP_COLORMAP=( + "" # 0 + "$GREEN" # 1 + "$BOLD_GREEN" # 2 + "$YELLOW" # 3 + "$BOLD_YELLOW" # 4 + "$RED" # 5 + "$BOLD_RED" # 6 + "$WARN_RED" # 7 + "$CRIT_RED" # 8 + "$DANGER_RED" # 9 + ) + fi + + # For mocking tests. + _LP_LINUX_POWERSUPPLY_PATH="/sys/class/power_supply" + _LP_LINUX_WIRELESS_FILE="/proc/net/wireless" + _LP_AIRPORT_BIN="/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport" + + if (( _LP_SHELL_zsh )); then + setopt local_options nullglob + fi + _LP_LINUX_TEMPERATURE_FILES=( + /sys/class/hwmon/hwmon*/temp*_input + # CentOS has an intermediate /device directory: + /sys/class/hwmon/hwmon*/device/temp*_input + /sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_input + # Older, fallback option + /sys/class/thermal/thermal_zone*/temp + ) + + # Debugging flags + LP_DEBUG_TIME=${LP_DEBUG_TIME:-0} + + if [[ ${1-} == --no-config ]]; then + return + fi + + # Default config file may be the XDG standard ~/.config/liquidpromptrc, + # but heirloom dotfile has priority. + local -a configfiles + configfiles=("$HOME/.liquidpromptrc" "${XDG_CONFIG_HOME:-"$HOME/.config"}/liquidpromptrc") + + # trailing ":" is so that ${search#*:} always removes something + local configfile search="${XDG_CONFIG_DIRS:-/etc/xdg}:" + while [[ -n "$search" ]]; do + configfiles+=("${search%%:*}/liquidpromptrc") + search="${search#*:}" + done + + configfiles+=("/etc/liquidpromptrc") + + for configfile in "${configfiles[@]}"; do + if [[ -f "$configfile" ]]; then + # shellcheck source=liquidpromptrc-dist + source "$configfile" + break + fi + done + + # Deprecations and compatibility shims + + if [[ -n "${LP_DISABLED_VCS_PATH-}" ]]; then + echo "liquidprompt: LP_DISABLED_VCS_PATH is deprecated. Update your config to use LP_DISABLED_VCS_PATHS array." >&2 + + (( _LP_SHELL_zsh )) && setopt local_options && setopt sh_word_split + local _path IFS=: + for _path in $LP_DISABLED_VCS_PATH; do + LP_DISABLED_VCS_PATHS+=("$_path") + done + fi + + # Delete this code in version 1.11 + if [[ -n "${LP_COLORMAP_1-}" ]]; then + echo "liquidprompt: LP_COLORMAP_x variables are deprecated. Update your theme to use LP_COLORMAP array." >&2 + LP_COLORMAP=( + "$LP_COLORMAP_0" + "$LP_COLORMAP_1" + "$LP_COLORMAP_2" + "$LP_COLORMAP_3" + "$LP_COLORMAP_4" + "$LP_COLORMAP_5" + "$LP_COLORMAP_6" + "$LP_COLORMAP_7" + "$LP_COLORMAP_8" + "$LP_COLORMAP_9" + ) + unset LP_COLORMAP_0 LP_COLORMAP_1 LP_COLORMAP_2 LP_COLORMAP_3 LP_COLORMAP_4 \ + LP_COLORMAP_5 LP_COLORMAP_6 LP_COLORMAP_7 LP_COLORMAP_8 LP_COLORMAP_9 + fi + + if [[ -n ${LP_PATH_DEFAULT-} ]]; then + echo "liquidprompt: LP_PATH_DEFAULT is deprecated. Update your config to set LP_PATH_METHOD." >&2 + if (( ! LP_ENABLE_SHORTEN_PATH )); then + # There is just no elegant way to handle this. Fallback to the old way with basic formatting support. + _lp_path_format() { + lp_path=$LP_PATH_DEFAULT + lp_path_format="${LP_COLOR_PATH}${lp_path}${NO_COL}" + } + fi + fi + + if [[ -n ${PROMPT_DIRTRIM-} ]] && (( ! LP_ENABLE_SHORTEN_PATH )); then + echo "liquidprompt: PROMPT_DIRTRIM support is deprecated. Update your config to set + LP_PATH_METHOD='truncate_chars_from_path_left' instead." >&2 + # This does mostly the same thing, but with our formatting. + LP_ENABLE_SHORTEN_PATH=1 + LP_PATH_METHOD="truncate_chars_from_path_left" + LP_PATH_KEEP=1 + fi + + if [[ $LP_PATH_KEEP == "-1" ]]; then + echo "liquidprompt: LP_PATH_KEEP set to '-1' is deprecated. Update your config to set + LP_PATH_METHOD='truncate_to_last_dir' instead." >&2 + LP_PATH_METHOD="truncate_to_last_dir" + fi + + if [[ ${LP_ENABLE_FQDN-} == 1 ]]; then + echo "liquidprompt: LP_ENABLE_FQDN is deprecated. Update your config to set LP_HOSTNAME_METHOD=full + or LP_HOSTNAME_METHOD=fqdn instead." >&2 + LP_HOSTNAME_METHOD="full" + fi +} + +# Initialize features based on the user config. +# shellcheck disable=SC2120 +lp_activate() { + if (( _LP_SHELL_bash )); then + complete -F __lp_theme_bash_complete lp_theme + else # zsh + # For ZSH, autoload required functions + autoload -Uz add-zsh-hook + + # Enable the autocomplete if the autocomplete system is initialized. + __lp_is_function compdef && compdef __lp_theme_zsh_complete lp_theme + fi + + # Disable hooks that we don't need if features will be disabled. + __lp_disable_hooks + + # TermInfo feature detection + _lp_af_colors=() _lp_ab_colors=() + + __lp_foreground_color() { return 2 ; } + __lp_background_color() { return 2 ; } + + # TODO handle this case better. With no colors, no need for any escaping + if ! command -v tput >/dev/null; then + echo "liquidprompt: 'tput' not available; will not be able to format terminal" >&2 + LP_ENABLE_COLOR=0 + else + _LP_TI_RESET="$( { tput sgr0 || tput me ; } 2>/dev/null )" + _LP_TI_BOLD="$( { tput bold || tput md ; } 2>/dev/null )" + _LP_TI_UNDERLINE="$( { tput smul || tput us ; } 2>/dev/null )" + _LP_TI_COLORS="$( tput colors 2>/dev/null )" + _LP_TI_COLORS=${_LP_TI_COLORS:-8} + + _LP_TI_BELL="$( { tput bel || tput bl ; } 2>/dev/null )" + + if tput setaf 0 >/dev/null 2>&1; then + __lp_foreground_color() { af_color="${_lp_af_colors[$1+1]:=$(tput setaf "$1")}"; } + elif tput AF 0 >/dev/null 2>&1; then + # FreeBSD + __lp_foreground_color() { af_color="${_lp_af_colors[$1+1]:=$(tput AF "$1")}"; } + elif tput AF 0 0 0 >/dev/null 2>&1; then + # OpenBSD + __lp_foreground_color() { af_color="${_lp_af_colors[$1+1]:=$(tput AF "$1" 0 0)}"; } + else + echo "liquidprompt: terminal '${TERM-}' does not support foreground colors" >&2 + fi + if tput setab 0 >/dev/null 2>&1; then + __lp_background_color() { ab_color="${_lp_ab_colors[$1+1]:=$(tput setab "$1")}"; } + elif tput AB 0 >/dev/null 2>&1; then + # FreeBSD + __lp_background_color() { ab_color="${_lp_ab_colors[$1+1]:=$(tput AB "$1")}"; } + elif tput AB 0 0 0 >/dev/null 2>&1; then + # OpenBSD + __lp_background_color() { ab_color="${_lp_ab_colors[$1+1]:=$(tput AB "$1" 0 0)}"; } + else + echo "liquidprompt: terminal '${TERM-}' does not support background colors" >&2 + fi + fi + + # If tput doesn't exist or lookup failed, still try to send bell + _LP_TI_BELL=${_LP_TI_BELL:-$'\a'} + + __lp_source_config "$@" + + # Disable feature if the tool is not installed + _lp_require_tool() + { + # zsh does not allow quoting here. + # shellcheck disable=SC1105,SC2086 + (( LP_ENABLE_$1 )) && { command -v "$2" >/dev/null || eval "LP_ENABLE_$1=0" ; } + } + + _lp_require_tool TIME date + + _lp_require_tool GIT git + _lp_require_tool SVN svn + _lp_require_tool FOSSIL fossil + _lp_require_tool HG "$LP_HG_COMMAND" + _lp_require_tool BZR bzr + _lp_require_tool CMAKE cmake + + _LP_ENABLED_VCSS=() + (( LP_ENABLE_GIT )) && _LP_ENABLED_VCSS+=(git) + (( LP_ENABLE_SVN )) && _LP_ENABLED_VCSS+=(svn) + (( LP_ENABLE_HG )) && _LP_ENABLED_VCSS+=(hg) + (( LP_ENABLE_BZR )) && _LP_ENABLED_VCSS+=(bzr) + + if [[ "$LP_OS" = Darwin ]]; then + _lp_require_tool BATT pmset + elif (( LP_ENABLE_BATT )); then + __lp_battery_detect || LP_ENABLE_BATT=0 + fi + + _lp_require_tool KUBECONTEXT kubectl + _lp_require_tool TERRAFORM terraform + + unset -f _lp_require_tool + + # LP_ENABLE_RUBY_VENV depends either from rvm or rbenv. Thus we cannot + # directly use _lp_require_tool for it. + # Also, to avoid to check twice which is the current ruby virtual env + # program in use, we set here an internal variable holding its name if we + # detect one of them. + if (( LP_ENABLE_RUBY_VENV )) ; then + if command -v rvm-prompt >/dev/null ; then + _LP_RUBY_VENV_PROGRAM=rvm + elif command -v rbenv >/dev/null ; then + _LP_RUBY_VENV_PROGRAM=rbenv + else + LP_ENABLE_RUBY_VENV=0 + fi + fi + + if (( LP_ENABLE_DETACHED_SESSIONS )); then + command -v screen >/dev/null ; _LP_ENABLE_SCREEN=$(( ! $? )) + command -v tmux >/dev/null ; _LP_ENABLE_TMUX=$(( ! $? )) + fi + + # Use standard path symbols inside Midnight Commander + [[ -n "${MC_SID-}" ]] && LP_ENABLE_SHORTEN_PATH=0 + + # If we are running in a terminal multiplexer, special title escapes + if _lp_multiplexer; then + (( LP_ENABLE_TITLE = LP_ENABLE_TITLE && LP_ENABLE_SCREEN_TITLE )) + LP_TITLE_OPEN=$'\Ek' + LP_TITLE_CLOSE=$'\E\\' + else + LP_TITLE_OPEN=$'\E]0;' + LP_TITLE_CLOSE=$'\a' + fi + + [[ "_${TERM-}" == _linux* ]] && LP_ENABLE_TITLE=0 + + # Can not show title command if the title feature is disabled. + (( LP_ENABLE_TITLE )) || LP_ENABLE_TITLE_COMMAND=0 + + # update_terminal_cwd is a shell function available on MacOS X Lion that + # will update an icon of the directory displayed in the title of the terminal + # window. + # See http://hints.macworld.com/article.php?story=20110722211753852 + if [[ "${TERM_PROGRAM-}" == Apple_Terminal ]] && command -v update_terminal_cwd >/dev/null; then + _LP_TERM_UPDATE_DIR=update_terminal_cwd + # Remove "update_terminal_cwd; " that has been add by Apple in /et/bashrc. + # See issue #196 + PROMPT_COMMAND="${PROMPT_COMMAND//update_terminal_cwd; /}" + else + _LP_TERM_UPDATE_DIR=: + fi + + ############### + # Who are we? # + ############### + + _lp_user + local -i user="$?" + + if (( user < 2 )); then # if user is not root + # "sudo -n" is only supported from sudo 1.7.0 + if (( LP_ENABLE_SUDO )); then + if command -v sudo >/dev/null \ + && LC_MESSAGES=C sudo -V \ + | GREP_OPTIONS='' \grep -qE '^Sudo version (1(\.([789]\.|[1-9][0-9])|[0-9])|[2-9])'; then + + # If user can run any command without password input. + # This command does not invalidate credentials. + if \sudo -nvk 2>/dev/null; then + _LP_SUDO_NOPASSWORD=1 + else + _LP_SUDO_NOPASSWORD=0 + fi + else + LP_ENABLE_SUDO=0 + fi + fi + + if (( user == 1 )); then + LP_COLOR_USER=$LP_COLOR_USER_ALT + else + LP_COLOR_USER=$LP_COLOR_USER_LOGGED + fi + else # root! + LP_ENABLE_SUDO=0 + if (( ! LP_ENABLE_VCS_ROOT )); then + LP_DISABLED_VCS_PATHS=("/") + fi + + LP_MARK_DEFAULT='#' + + LP_COLOR_MARK=$LP_COLOR_MARK_ROOT + LP_COLOR_PATH=$LP_COLOR_PATH_ROOT + LP_COLOR_USER=$LP_COLOR_USER_ROOT + fi + + ################# + # Where are we? # + ################# + + if (( LP_ENABLE_TEMP )); then + # Try each _lp_temp method + # If no function worked, disable the feature + __lp_temp_detect || LP_ENABLE_TEMP=0 + fi + + if (( LP_ENABLE_LOAD )); then + # Find and save the number of CPUs + __lp_cpu_count + + # Convert load config values into usable integers. + local ret + # if load threshold does not have a period in it. + if [[ -z ${LP_LOAD_THRESHOLD//[!\.]} ]]; then + # This is an old value, no need to convert + _LP_LOAD_THRESHOLD=$LP_LOAD_THRESHOLD + else + __lp_floating_scale "$LP_LOAD_THRESHOLD" 100 + _LP_LOAD_THRESHOLD=$ret + fi + __lp_floating_scale "$LP_LOAD_CAP" 100 + _LP_LOAD_CAP=$ret + fi + + if [[ -n ${_LP_THEME_ACTIVATE_FUNCTION-} ]]; then + # Reactivate current theme + "$_LP_THEME_ACTIVATE_FUNCTION" + prompt_on + else + # Set default theme if no theme set + lp_theme default + fi +} + +##################### +# Utility Functions # +##################### + +# Remove all colors and escape characters of the given string and return a pure text +# Deprecated since v2.1. +_lp_as_text() { + # Remove all terminal sequences that we wrapped with $_LP_OPEN_ESC and + # $_LP_CLOSE_ESC. + local ret + __lp_strip_escapes "$1" + printf '%s' "$ret" +} + +# Store $2 (or $?) as a true/false value in variable named $1 +# Deprecated since v2.0. +_lp_bool() { + local res="${2:-$?}" + if (( res )); then + eval "$1=false" + else + eval "$1=true" + fi + return "$res" +} + +_lp_color_map() { + # Default scale: 0..100 + # Custom scale: 0..$2 + local -i scale value + scale=${2:-100} + if (( $1 >= scale )); then + (( value = scale - 1 )) + elif (( $1 < 0 )); then + value=0 + else + value=$1 + fi + # Transform the value to a 0..${#COLOR_MAP} scale + ret="${LP_COLORMAP[_LP_FIRST_INDEX+value*${#LP_COLORMAP[*]}/scale]}" +} + +# Generate a terminal hyperlink from a URL and text. +_lp_create_link() { # url, text + (( LP_ENABLE_HYPERLINKS )) || return 2 + lp_link="$_LP_OPEN_ESC"$'\E]8;;'"${1}"$'\E'"${_LP_BACKSLASH}${_LP_CLOSE_ESC}${2}${_LP_OPEN_ESC}"$'\E]8;;\E'"${_LP_BACKSLASH}$_LP_CLOSE_ESC" +} + +# Add a link to a path element, with protocol depending on the connection. +_lp_create_link_path() { # path + local _path="$1" + if (( LP_ENABLE_HYPERLINKS )); then + _lp_connection + + if [[ "$lp_connection" == "ssh" ]]; then + # SSH connection needs the `sftp` protocol to reach the remote host. + local client_ip client_port server_ip server_port + IFS=" " read -r client_ip client_port server_ip server_port <<<"$SSH_CONNECTION" + local username=${USER:-${USERNAME:-${LOGNAME-}}} + _lp_create_link "sftp://${username}@${server_ip}:${client_port}/${PWD}/" "$_path" + lp_link_path="$lp_link" + + elif [[ "$lp_connection" == "su" || "$lp_connection" == "lcl" ]]; then + # Reach local host with the `file` protocol. + _lp_create_link "file://${PWD}/" "$_path" + lp_link_path="$lp_link" + + else # no link for telnet. + lp_link_path="$_path" + fi + else # No link. + lp_link_path="$_path" + fi +} + +# Return true if the input is the name of a function. +__lp_is_function() { + if (( _LP_SHELL_bash )); then + [[ $(LC_ALL=C \type -t "$1") == function ]] + else + [[ $(LC_ALL=C \type -w "$1") == *function ]] + fi +} + +# Count the number of lines in the input string. A faster substitute for 'wc -l'. +__lp_line_count() { + local var="${1//[!$'\n']}" + count=${#var} +} + +__lp_hostname_hash() { + # cksum is separated with tab on SunOS, space on others + local cksum="$(hostname | cksum)" + lp_hostname_hash=${cksum%%[$' \t']*} +} + +__lp_floating_scale() { + local integer decimal=0 scale=$(( ${#2} - 1 )) + integer=${1%\.*} + + if [[ -n ${1//[!\.]} ]]; then + decimal=${1#*\.} + decimal=${decimal:0:$scale} + + while (( ${#decimal} < scale )); do + decimal+='0' + done + + while [[ ${decimal:0:1} == '0' ]]; do + decimal=${decimal:1} + done + fi + + ret=$(( integer * $2 + decimal )) +} + +# Return $PWD with $HOME at the start replaced by "~". +__lp_pwd_tilde() { # [path] + # Needs to be in a variable, as different versions of Bash treat '~' in a + # substitution differently + local _path="${1:-$PWD}" tilde="~" + lp_pwd_tilde="${_path/#$HOME/$tilde}" +} + +# insert a space on the right +# Deprecated since v2.0. +_lp_sr() { + [[ -n "$1" ]] && echo -nE "$1 " +} + +# insert a space on the left +# Deprecated since v2.0. +_lp_sl() { + [[ -n "$1" ]] && echo -nE " $1" +} + +# insert two spaces, before and after +# Deprecated since v2.0. +_lp_sb() { + [[ -n "$1" ]] && echo -nE " $1 " +} + +# Generates a terminal escape sequence to format the terminal. +lp_terminal_format() { # fg, bg, bold, underline, fallback_fg, fallback_bg + lp_terminal_format= + (( LP_ENABLE_COLOR )) || return 2 + + local af_color ab_color fg bg previous_af_color + fg=$1 + bg=${2:-"-1"} + previous_af_color=${_lp_last_af_color-} + + lp_terminal_format=${_LP_OPEN_ESC}${_LP_TI_RESET} + + if (( fg >= _LP_TI_COLORS )) && [[ -n ${5-} ]]; then + _lp_last_af_color=$5 + elif (( fg == -2 )); then + : # do nothing, _lp_last_af_color already correct + elif (( fg == -3 )); then + _lp_last_af_color=$_lp_last_ab_color + elif (( fg >= 0 )); then + _lp_last_af_color=$fg + else # -1 + _lp_last_af_color=-1 + fi + + if (( ${_lp_last_af_color:-"-1"} >= 0 )); then + __lp_foreground_color "$_lp_last_af_color" && lp_terminal_format+=$af_color + fi + + if (( bg >= _LP_TI_COLORS )) && [[ -n ${6-} ]]; then + _lp_last_ab_color=$6 + elif (( bg == -2 )); then + : # do nothing, _lp_last_ab_color already correct + elif (( bg == -3 )); then + _lp_last_ab_color=$previous_af_color + elif (( bg >= 0 )); then + _lp_last_ab_color=$bg + else # -1 + _lp_last_ab_color=-1 + fi + + if (( ${_lp_last_ab_color:-"-1"} >= 0 )); then + __lp_background_color "$_lp_last_ab_color" && lp_terminal_format+=$ab_color + fi + + # It turns out there are sequences to reset bold and underline to normal + # (\E[22m and \E[24m in xterm), but they aren't universally supported. This + # means we must reset to all defaults then enable if they are wanted. + # Explicit is safer anyway. + if (( ${3:-0} )); then + lp_terminal_format+=$_LP_TI_BOLD + fi + + if (( ${4:-0} )); then + lp_terminal_format+=$_LP_TI_UNDERLINE + fi + + lp_terminal_format+=$_LP_CLOSE_ESC +} + +# Get a list of themes currently loaded. Looks for functions matching +# _lp__theme_prompt(). +__lp_theme_list() { + lp_theme_list=() + + local -a _functions + if (( _LP_SHELL_zsh )); then + # shellcheck disable=SC2296 + _functions=( "${(ko)functions[@]}" ) + else + local IFS=$'\n' + # shellcheck disable=SC2207 + _functions=( $(declare -F) ) + fi + + local function + for function in "${_functions[@]}"; do + if [[ $function == *_lp_*_theme_prompt ]]; then + function=${function#*_lp_} + lp_theme_list+=("${function%_theme_prompt}") + fi + done +} + +__lp_theme_bash_complete() { + COMPREPLY=() + local -a lp_theme_list + local theme partial_theme + partial_theme=${2-} + + __lp_theme_list + + for theme in "${lp_theme_list[@]}"; do + [[ -n $partial_theme && $theme != "$partial_theme"* ]] && continue + COMPREPLY+=("$theme") + done +} + +__lp_theme_zsh_complete() { + local -a lp_theme_list + __lp_theme_list + _describe 'theme' lp_theme_list +} + +_lp_hash_color() { + (( LP_ENABLE_COLOR )) || return 2 + # compute the hash of the argument and color it with the corresponding color in: + # (green, yellow, blue, purple, cyan) + # Red (would be index 1) is not used here, as it should be reserved for alerts. + local str=${1-} + local cksum="$(printf '%s' "$str" | cksum)" + local hash="${cksum%%[$' \t']*}" + __lp_foreground_color "$(( 2 + hash % 5 ))" + local color="${_LP_OPEN_ESC}${af_color-}${_LP_CLOSE_ESC}" + lp_hash_color="${color}${str}${NO_COL}" +} + +_lp_join() { + # Join the $2... array with the $1 string. + local delimiter=${1-} + local IFS="" + if (( _LP_SHELL_zsh )); then + shift 1 + # shellcheck disable=SC2296 + lp_join="${(pj/$delimiter/)*}" + else + local first=${2-} + shift 2 + lp_join="${first}${*/#/$delimiter}" + fi +} + +_lp_grep_fields() { + # Open file $1 and parse it for key/value pairs having delimiter $2, for each key in $3... + # Silently bypass inexisting fields. + local file="$1" + [[ -f "$file" ]] || return 1 + + local delimiter="$2" + shift 2 + local -a fields + fields=("$@") + + lp_grep_fields=() + # We test for $line in the loop is here to ensure that we read the last line, + # even if the file does not ends with a \n. + # This bypass a known behavior of the C standard, not fixed in POSIX. + while IFS='' read -r line || [[ -n "$line" ]] ; do + for (( i=_LP_FIRST_INDEX; i < ${#fields[@]} + _LP_FIRST_INDEX; i++ )); do + key="${fields[i]}" + if [[ "$line" == "${key}${delimiter}"* ]] ; then + line="${line#*"${key}${delimiter}"}" # Remove first part until delimiter. + lp_grep_fields[i]="${line}" + fi + done + done <"$file" +} + +# Adds as much $3 character (e.g. spaces) between $1 and $2, +# so as to make the resulting string the same width as the current terminal. +_lp_fill() { + + local left="$1" right="$2" fillchars="${3:-" "}" splitends="${4:-1}" + + # Compute the gap between left and right. + local ret + __lp_strip_escapes "$left" + local left_as_text="$ret" + __lp_strip_escapes "$right" + local right_as_text="$ret" + local gap_width=$((${COLUMNS:-80}-${#left_as_text}-${#right_as_text})) + if [[ $gap_width -lt 0 ]] ; then + lp_fill="${left}${right}" + return 1 + fi + + local filled="" + __lp_strip_escapes "$fillchars" + local fillchars_as_text="${ret}" + local fillchars_as_text_width=${#fillchars_as_text} + local nb_fillchars=$((gap_width/fillchars_as_text_width)) + local i + for (( i=0; i < nb_fillchars; i++ )) ; do + filled+="$fillchars" + done + + __lp_strip_escapes "$filled" + local actual_width=${#ret} + # If there is still a gap (i.e. we have an unaligned multi-character fillchars). + if [[ ${actual_width} -lt ${gap_width} ]] ; then + # User asked for splitends. + if [[ $splitends -ne 0 ]] ; then + # The last occurence is necessarily stripped from escaped sequences, + # or else we may have dangling sequences. + for (( i=_LP_FIRST_INDEX; i < $((${#fillchars_as_text}+_LP_FIRST_INDEX)); i++ )) ; do + # Get one single character. + if (( _LP_SHELL_zsh )) ; then + filled+="${fillchars_as_text[i,i]}" + else + filled+="${fillchars_as_text:i:1}" + fi + __lp_strip_escapes "$filled" + actual_width=${#ret} + if [[ ${actual_width} -ge ${gap_width} ]] ; then + # Stop at this char if we're full. + break + fi + done + else # User asked for no splitends. + for (( i=actual_width; i < gap_width; i++ )) ; do + # Fill with spaces. + filled+=" " + done + fi + fi + + # shellcheck disable=SC2034 + lp_fill="${left}${filled}${right}" +} + +_lp_version_string() { # [major, [minor, [patch, [string, [number]]]]] + + if [[ $# -eq 0 ]]; then + local major="${_LP_VERSION[_LP_FIRST_INDEX+0]}" + local minor="${_LP_VERSION[_LP_FIRST_INDEX+1]}" + local patch="${_LP_VERSION[_LP_FIRST_INDEX+2]-}" + local string="${_LP_VERSION[_LP_FIRST_INDEX+3]-}" + local number="${_LP_VERSION[_LP_FIRST_INDEX+4]-}" + else + local major="${1}" + local minor="${2}" + local patch="${3-}" + local string="${4-}" + local number="${5-}" + fi + #shellcheck disable=SC2034 + lp_version="${major}.${minor}" + if [[ -n "$patch" ]]; then + lp_version+=".${patch}" + if [[ -n "$string" ]]; then + lp_version+="-${string}" + if [[ -n "$number" ]]; then + lp_version+=".${number}" + fi + fi + fi +} + +_lp_substitute() { # string pairs_array + # Replace string with the "right" part of the first pair in the given array, if the "left" part matches the string. + # FIXME If we ever support Bash>4.3, this would be better implemented with associative arrays. + lp_substitute="" + local string="${1}" + shift 1 + local pairs + pairs=("$@") + [[ -n "${pairs[*]}" ]] || return 2 + + local i key + for (( i=0; i < ${#pairs[@]}; i+=2 )); do + key=${pairs[_LP_FIRST_INDEX+i]} + if [[ "$key" == "$string" ]]; then + lp_substitute=${pairs[_LP_FIRST_INDEX+i+1]-} + return 0 # Found. + fi + done + return 1 # Not found. +} + + +########################## +# Working Directory Path # +########################## + +__lp_get_unique_directory() { + local directory="${1##*/}" root="${1%/*}" + local -a matching + local -i i + for (( i=1; i < ${#directory}; i++ )); do + lp_unique_directory=${directory:0:$i} + matching=("${root}/${lp_unique_directory}"*/) + if (( ${#matching[@]} == 1 )); then + return 0 + fi + done + + return 1 +} + +__lp_end_path_left_shortening() { + if (( is_shortening )); then + shortened_path_length+=${#separator} + if (( shortened_path_length < unshortened_path_length )); then + lp_path_format+="${shortened_directory_format}${LP_MARK_SHORTEN_PATH}" + # This indescriminate adding of a separator can sometimes mean the path + # is slightly longer than it should be, but it is more clear. + lp_path_format+="${separator_format}${separator}" + lp_path+="${LP_MARK_SHORTEN_PATH}/" + else + lp_path+=$unshortened_path_shorten_string + lp_path_format+=$unshortened_path_format_shorten_string + shortened_path_length=$unshortened_path_length + fi + is_shortening=0 + fi +} + +# methods: +# truncate_chars_from_path_left +# truncate_chars_from_dir_middle +# truncate_chars_from_dir_right +# truncate_chars_to_unique_dir +# truncate_to_last_dir +_lp_path_format() { + local path_format="${1-$LP_COLOR_PATH}" + local last_directory_format="${2:-$path_format}" + local vcs_root_format="${3:-$last_directory_format}" + local shortened_directory_format="${4:-$path_format}" + local separator="${5-"/"}" + local separator_format="${6-}" + + lp_path= + lp_path_format= + + local ret lp_link_path + + local lp_pwd_tilde + __lp_pwd_tilde + local display_path="$lp_pwd_tilde" + + local -i path_length="${#display_path}" + + local lp_vcs_root lp_vcs_dir lp_vcs_type lp_vcs_subtype + local vcs_root_directory= + if (( LP_PATH_VCS_ROOT )) && _lp_find_vcs; then + __lp_pwd_tilde "$lp_vcs_root" + vcs_root_directory=$lp_pwd_tilde + fi + + if [[ $path_length -eq 1 || $LP_PATH_METHOD == "truncate_to_last_dir" ]]; then + if [[ $path_length -gt 1 ]]; then + lp_path=${display_path##*/} + else + # only root or home to show + lp_path=$display_path + fi + + __lp_escape "$lp_path" + + if [[ $display_path == "$vcs_root_directory" ]]; then + _lp_create_link_path "${ret}" + lp_path_format="${vcs_root_format}$lp_link_path" + else + _lp_create_link_path "${ret}" + lp_path_format="${last_directory_format}$lp_link_path" + fi + + return + else + if [[ $separator != "/" && ${display_path:0:1} == "/" ]]; then + # The root directory ('/') becomes a directory name instead of a leading separator + # Add one to account for the first / needing to be both replaced and shown + path_length+=1 + fi + if [[ ${#separator} -gt 1 ]]; then + # Add length to account for multichar separators + local slash_count="${display_path//[!\/]}" + path_length+=$(( ${#slash_count} * ( ${#separator} - 1 ) )) + fi + fi + + local path_to_proccess="${display_path}/" current_path="" current_directory="" lp_unique_directory + + local -i max_len=$(( ${COLUMNS:-80} * LP_PATH_LENGTH / 100 )) directory_count=0 needed_length + local -i shortened_path_length="$path_length" is_shortening=0 unshortened_path_length + local unshortened_path_shorten_string unshortened_path_format_shorten_string shortened_path + + while [[ -n $path_to_proccess ]]; do + + if [[ ${path_to_proccess:0:1} == "/" ]]; then + # Start of root + current_directory="/" + else + current_directory=${path_to_proccess%%/*} + if [[ -n $current_path && $current_path != "/" ]]; then + current_path+="/" + fi + fi + + directory_count+=1 + current_path+=${current_directory} + path_to_proccess=${path_to_proccess#*/} + + if [[ $current_path == "$vcs_root_directory" ]]; then + __lp_end_path_left_shortening + # No shortening + lp_path+=$current_directory + __lp_escape "$current_directory" + lp_path_format+="${vcs_root_format}" + elif [[ -z $path_to_proccess ]]; then + __lp_end_path_left_shortening + # Last directory + lp_path+=$current_directory + __lp_escape "$current_directory" + lp_path_format+="${last_directory_format}" + elif (( LP_ENABLE_SHORTEN_PATH && directory_count > LP_PATH_KEEP \ + && ( shortened_path_length > max_len || ( shortened_path_length >= max_len && is_shortening ) ) )); then + + if [[ $LP_PATH_METHOD == "truncate_chars_to_unique_dir" ]] && \ + __lp_get_unique_directory "$current_path"; then + + lp_path+=$lp_unique_directory + __lp_escape "$lp_unique_directory" + lp_path_format+="${shortened_directory_format}" + shortened_path_length=$(( shortened_path_length - ${#current_directory} + ${#lp_unique_directory} )) + elif [[ $LP_PATH_METHOD == "truncate_chars_from_path_left" ]]; then + # The only way to know if this consecutive directory shortening + # will actually shorten the path is to both do it and do not and + # compare at the end. + + if (( ! is_shortening )); then + unshortened_path_shorten_string= + unshortened_path_format_shorten_string= + unshortened_path_length=$shortened_path_length + shortened_path_length+=${#LP_MARK_SHORTEN_PATH} + fi + needed_length=$(( shortened_path_length - max_len )) + + unshortened_path_shorten_string+="${current_directory}/" + unshortened_path_format_shorten_string+="${path_format}${current_directory}${separator_format}${separator}" + + if (( needed_length >= ${#current_directory} )); then + # One directory was not enough, need to shorten more. + # Shorten by current directory length plus separator. + shortened_path_length=$(( shortened_path_length - ${#current_directory} - ${#separator} )) + is_shortening=1 + else + # Do not need to check if the shortened version is actually shorter. + # If we got to here, it wasn't a forced ending, which means it is. + shortened_path_length=$(( shortened_path_length - needed_length )) + + shortened_path="${LP_MARK_SHORTEN_PATH}${current_directory:$needed_length}" + lp_path+=$shortened_path + __lp_escape "$shortened_path" + lp_path_format+="${shortened_directory_format}" + + is_shortening=0 + fi + elif [[ $LP_PATH_METHOD == "truncate_chars_from_dir_right" ]] && \ + (( ${#LP_MARK_SHORTEN_PATH} + LP_PATH_CHARACTER_KEEP < ${#current_directory} )); then + + shortened_path="${current_directory:0:$LP_PATH_CHARACTER_KEEP}${LP_MARK_SHORTEN_PATH}" + lp_path+=$shortened_path + __lp_escape "$shortened_path" + lp_path_format+="${shortened_directory_format}" + shortened_path_length=$(( shortened_path_length - ${#current_directory} + ${#LP_MARK_SHORTEN_PATH} + LP_PATH_CHARACTER_KEEP )) + elif [[ $LP_PATH_METHOD == "truncate_chars_from_dir_middle" ]] && \ + (( ${#LP_MARK_SHORTEN_PATH} + LP_PATH_CHARACTER_KEEP * 2 < ${#current_directory} )); then + + shortened_path="${current_directory:0:$LP_PATH_CHARACTER_KEEP}${LP_MARK_SHORTEN_PATH}${current_directory: -$LP_PATH_CHARACTER_KEEP}" + lp_path+=$shortened_path + __lp_escape "$shortened_path" + lp_path_format+="${shortened_directory_format}" + shortened_path_length=$(( shortened_path_length - ${#current_directory} + ${#LP_MARK_SHORTEN_PATH} + LP_PATH_CHARACTER_KEEP * 2 )) + else + # Need to shorten, but no method matched, or the matched method + # did not make the string any shorter. + lp_path+=$current_directory + __lp_escape "$current_directory" + lp_path_format+="${path_format}" + fi + else + __lp_end_path_left_shortening + lp_path+=$current_directory + __lp_escape "$current_directory" + lp_path_format+="${path_format}" + fi + + if (( ! is_shortening )); then + + _lp_create_link_path "$ret" + lp_path_format+="$lp_link_path" + + if [[ -n $path_to_proccess && ( $current_path != "/" || $separator != "/" ) ]]; then + if [[ $current_path != "/" ]]; then + lp_path+="/" + fi + lp_path_format+="${separator_format}${separator}" + fi + fi + done +} + +############### +# Environment # +############### + +# If we are connected with a X11 support +_lp_connected_display() { + [[ -n "${DISPLAY-}" ]] +} + +_lp_connection() { + if [[ -n "${SSH_CLIENT-}${SSH2_CLIENT-}${SSH_TTY-}" ]]; then + lp_connection=ssh + return + elif [[ -n ${REMOTEHOST-} ]]; then + lp_connection=tel # Telnet + return + fi + + local sess_parent="$(ps -o comm= -p "$PPID" 2> /dev/null)" + if [[ "$sess_parent" = "su" || "$sess_parent" = "sudo" ]]; then + lp_connection=su # su/sudo + else + lp_connection=lcl # Local + fi +} + +_lp_chroot() { + if [[ -r /etc/debian_chroot ]]; then + IFS= read -r lp_chroot [ ]" + # Strings may be `%s`, which will be replaced by the variable's actual content. + + (( LP_ENABLE_ENV_VARS )) || return 2 + + local color_set="${1-}" + local color_unset="${2-}" + local color_no="${NO_COL}" + if [[ -z "$color_set" && -z "$color_unset" ]]; then + color_no="" + fi + + local var_rep var evar fmt_if_set fmt_if_unset + + lp_env_vars=() + # For all user-defined setup. + for var_rep in ${LP_ENV_VARS[@]+"${LP_ENV_VARS[@]}"}; do + IFS=' ' read -r var fmt_if_set fmt_if_unset <<<"$var_rep" + # Variable name and set format has to be set, but unset format is optional. + if [[ -n "${var}" && -n "${fmt_if_set}" ]]; then + # Expands the underlying variable name. + local var_is_set= + if (( _LP_SHELL_zsh )); then + # From https://www.shellcheck.net/wiki/SC2296 : + # "Some Zsh specific parameter expansions like ${(q)value} trigger this warning, + # but ShellCheck does not support Zsh." + # shellcheck disable=SC2296 + var_is_set="${(P)var+IS_SET}" + else + # NOTE: indirection expansion are deprecated starting at bash 4.3, should use nameref. + var_is_set="${!var+IS_SET}" + fi + + if [[ -n "$var_is_set" ]]; then + if [[ "${fmt_if_set}" == *"%s"* ]]; then + local evar= + if (( _LP_SHELL_zsh )); then + # shellcheck disable=SC2296 + evar="${(P)var}" + else + evar="${!var}" + fi + # Print content. + lp_env_vars+=( "${color_set}${fmt_if_set/\%s/${evar}}${color_no}" ) + else + # Print tag. + lp_env_vars+=( "${color_set}${fmt_if_set}${color_no}" ) + fi + elif [[ -n "$fmt_if_unset" ]]; then + # Print default. + lp_env_vars+=( "${color_unset}${fmt_if_unset}${color_no}" ) + fi + fi + done + if [[ -z "${lp_env_vars-}" ]]; then + return 1 + fi +} + +_lp_env_vars_color() { + if _lp_env_vars "${LP_COLOR_ENV_VARS_SET}" "${LP_COLOR_ENV_VARS_UNSET}"; then + _lp_join "${LP_MARK_ENV_VARS_SEP}" "${lp_env_vars[@]}" + lp_env_vars_color="${LP_MARK_ENV_VARS_OPEN}${lp_join}${LP_MARK_ENV_VARS_CLOSE}" + else + return "$?" + fi +} + + +_lp_python_env() { + (( LP_ENABLE_VIRTUALENV )) || return 2 + + local ret + + # Truncate to the last '/' section, which is the directory name. + if [[ -n "${VIRTUAL_ENV-}" ]]; then + virtualenv_config="${VIRTUAL_ENV}/pyvenv.cfg" + if [[ -r "$virtualenv_config" ]]; then + local line + while IFS='' read -r line ; do + if [[ $line == "prompt"*"="* ]]; then + line="${line#"prompt"*"="}" + line="${line#[[:space:]]}" + line="${line#[\'\"]}" + line="${line%[\'\"]}" + __lp_escape "$line" + break + fi + done <"$virtualenv_config" + fi + + if [[ -z "${ret-}" ]]; then + __lp_escape "${VIRTUAL_ENV##*/}" + fi + + lp_python_env=$ret + elif [[ -n "${CONDA_DEFAULT_ENV-}" ]]; then + __lp_escape "${CONDA_DEFAULT_ENV##*/}" + lp_python_env=$ret + else + return 1 + fi +} + +_lp_python_env_color() { + unset lp_python_env_color + _lp_python_env || return "$?" + + lp_python_env_color="${LP_COLOR_VIRTUALENV}${lp_python_env}${NO_COL}" +} + +_lp_node_env() { + (( LP_ENABLE_NODE_VENV )) || return 2 + + local ret + + if [[ -n "${NODE_VIRTUAL_ENV-}" ]]; then + # Truncate to the last '/' section, which is the directory name. + __lp_escape "${NODE_VIRTUAL_ENV##*/}" + lp_node_env=$ret + elif [[ -n "${NVM_BIN-}" ]]; then + # Get the version string from the path. + ret="${NVM_BIN##*/node/}" + __lp_escape "${ret%/bin}" + lp_node_env=$ret + else + return 1 + fi +} + +_lp_node_env_color() { + unset lp_node_env_color + _lp_node_env || return "$?" + + lp_node_env_color="${LP_COLOR_NODE_VENV}${lp_node_env}${NO_COL}" +} + +_lp_ruby_env() { + (( LP_ENABLE_RUBY_VENV )) || return 2 + + local ret + + if [[ "$_LP_RUBY_VENV_PROGRAM" = "rvm" ]] ; then + __lp_escape "$(rvm-prompt "${LP_RUBY_RVM_PROMPT_OPTIONS[@]}")" + lp_ruby_env=$ret + elif [[ "$_LP_RUBY_VENV_PROGRAM" = "rbenv" ]] ; then + local rbenv_ver="$(rbenv version)" + # Only first word is necessary + __lp_escape "${rbenv_ver%%" (set"*}" + lp_ruby_env=$ret + else + return 1 + fi +} + +_lp_ruby_env_color() { + unset lp_ruby_env_color + _lp_ruby_env || return "$?" + + lp_ruby_env_color="${LP_COLOR_RUBY_VENV}${lp_ruby_env}${NO_COL}" +} + +_lp_terraform_env() { + (( LP_ENABLE_TERRAFORM )) || return 2 + + local ret + + if [[ -d .terraform ]]; then + local _tf_workspace + _tf_workspace="$(\terraform workspace show 2>/dev/null)" + if [[ -n "$_tf_workspace" ]]; then + __lp_escape "${_tf_workspace}" + lp_terraform_env=$ret + else + return 1 + fi + else + return 1 + fi +} + +_lp_terraform_env_color() { + unset lp_terraform_env_color + _lp_terraform_env || return "$?" + + lp_terraform_env_color="${LP_COLOR_TERRAFORM}${lp_terraform_env}${NO_COL}" +} + +_lp_software_collections() { + (( LP_ENABLE_SCLS )) || return 2 + + if [[ -n "${X_SCLS-}" ]]; then + local ret + __lp_escape "${X_SCLS%"${X_SCLS##*[![:space:]]}"}" + lp_software_collections=$ret + else + return 1 + fi +} + +_lp_software_collections_color() { + unset lp_software_collections_color + _lp_software_collections || return "$?" + + lp_software_collections_color="${LP_COLOR_VIRTUALENV}${lp_software_collections}${NO_COL}" +} + + +_lp_kubernetes_context() { + (( LP_ENABLE_KUBECONTEXT )) || return 2 + + local kubernetes_context + + if (( LP_ENABLE_KUBE_NAMESPACE )); then + local line kubernetes_namespace + line=$(kubectl config view --minify --output \ + 'jsonpath={.current-context}{"/"}{..namespace}' 2>/dev/null) || return 1 + kubernetes_context=${line%/*} + kubernetes_namespace=${line##*/} + else + kubernetes_context=$(kubectl config current-context 2>/dev/null) || return 1 + fi + + if [[ -n "$LP_DELIMITER_KUBECONTEXT_PREFIX" ]]; then + # shellcheck disable=SC2295 + kubernetes_context="${kubernetes_context##*${LP_DELIMITER_KUBECONTEXT_PREFIX}}" + fi + + if [[ -n "$LP_DELIMITER_KUBECONTEXT_SUFFIX" ]]; then + # shellcheck disable=SC2295 + kubernetes_context="${kubernetes_context%%${LP_DELIMITER_KUBECONTEXT_SUFFIX}*}" + fi + + local ret + __lp_escape "$kubernetes_context" + lp_kubernetes_context=$ret + + if [[ -n ${kubernetes_namespace-} ]]; then + __lp_escape "$kubernetes_namespace" + lp_kubernetes_namespace=$ret + else + unset lp_kubernetes_namespace + fi +} + +_lp_kubernetes_context_color() { + unset lp_kubernetes_context_color + _lp_kubernetes_context || return "$?" + + lp_kubernetes_context_color="${LP_MARK_KUBECONTEXT}${LP_COLOR_KUBECONTEXT}${lp_kubernetes_context}${lp_kubernetes_namespace+:}${lp_kubernetes_namespace-}${NO_COL}" +} + + +_lp_cmake() { + (( LP_ENABLE_CMAKE )) || return 2 + [[ -f CMakeCache.txt ]] || return 1 + + _lp_grep_fields "CMakeCache.txt" "=" "CMAKE_C_COMPILER:FILEPATH" "CMAKE_CXX_COMPILER:FILEPATH" "CMAKE_GENERATOR:INTERNAL" "CMAKE_BUILD_TYPE:STRING" + + local cmake_c_compiler=${lp_grep_fields[_LP_FIRST_INDEX+0]-} + lp_cmake_c_compiler="${cmake_c_compiler##*/}" # Only the part after the last slash. + + local cmake_cxx_compiler=${lp_grep_fields[_LP_FIRST_INDEX+1]-} + lp_cmake_cxx_compiler="${cmake_cxx_compiler##*/}" # Only the part after the last slash. + + local cmake_generator=${lp_grep_fields[_LP_FIRST_INDEX+2]-} + # Shorten: Makefiles -> Make, and Visual Studio -> VS. + cmake_generator="${cmake_generator/%Makefiles/Make}" + cmake_generator="${cmake_generator/#Visual Studio/VS}" + # Remove all white spaces. + lp_cmake_generator="${cmake_generator// /}" + + lp_cmake_buildtype=${lp_grep_fields[_LP_FIRST_INDEX+3]-} + [[ -n "${lp_cmake_c_compiler}${lp_cmake_cxx_compiler}${lp_cmake_generator}${lp_cmake_buildtype}" ]] || return 1 +} + +_lp_cmake_color() { + unset lp_cmake_color + _lp_cmake || return "$?" + + local lp_hash_color + + lp_cmake_color="" + + if [[ -n "$lp_cmake_c_compiler" ]] ; then + _lp_hash_color "$lp_cmake_c_compiler" + lp_cmake_color+="${lp_hash_color}${LP_MARK_CMAKE}" + fi + + if [[ -n "$lp_cmake_cxx_compiler" ]] ; then + _lp_hash_color "$lp_cmake_cxx_compiler" + lp_cmake_color+="${lp_hash_color}${LP_MARK_CMAKE}" + fi + + if [[ -n "$lp_cmake_generator" ]] ; then + _lp_hash_color "$lp_cmake_generator" + lp_cmake_color+="$lp_hash_color${LP_MARK_CMAKE}" + fi + + if [[ -n "$lp_cmake_buildtype" ]] ; then + if [[ "$lp_cmake_buildtype" == "Release" ]] ; then + lp_cmake_color+="${LP_COLOR_CMAKE_RELEASE}${lp_cmake_buildtype}${NO_COL}" + elif [[ "$lp_cmake_buildtype" == "RelWithDebInfo" ]] ; then + lp_cmake_color+="${LP_COLOR_CMAKE_RWDI}${lp_cmake_buildtype}${NO_COL}" + elif [[ "$lp_cmake_buildtype" == "Debug" ]] ; then + lp_cmake_color+="${LP_COLOR_CMAKE_DEBUG}${lp_cmake_buildtype}${NO_COL}" + else + _lp_hash_color "$lp_cmake_buildtype" + lp_cmake_color+="$lp_hash_color" + fi + fi +} + +_lp_modules() { + (( LP_ENABLE_MODULES )) || return 2 + lp_modules=() + # # Module sets the LOADEDMODULES environment variable. + if [[ -n "${LOADEDMODULES-}" ]]; then + # Outer test should be faster. + if (( LP_ENABLE_MODULES_VERSIONS )); then + local IFS=':' + for mod in $LOADEDMODULES; do + lp_modules+=("${mod}") + done + else + local IFS=':' + for mod in $LOADEDMODULES; do + # Simply remove the part after the slash. + # Should work on both Bash and Zsh. + lp_modules+=("${mod%/*}") + done + fi + if (( ${#lp_modules[@]} == 0 )); then + return 1 + fi + else + return 1 + fi +} + +_lp_modules_color() { + lp_modules_color= + if _lp_modules; then + if (( LP_ENABLE_COLOR )); then + local lp_join + if (( LP_ENABLE_MODULES_HASHCOLOR )); then + local modules=() + for mod in "${lp_modules[@]}"; do + _lp_hash_color "${mod}" + modules+=("${lp_hash_color}") + done + # Do not color marks and separators. + _lp_join "${NO_COL}${LP_MARK_MODULES_SEP}" "${modules[@]}" + lp_modules_color="${LP_MARK_MODULES_OPEN}${lp_join}${NO_COL}${LP_MARK_MODULES_CLOSE}" + else # No hashcolor. + _lp_join "${NO_COL}${LP_MARK_MODULES_SEP}${LP_COLOR_MODULES}" "${lp_modules[@]}" + lp_modules_color="${LP_MARK_MODULES_OPEN}${LP_COLOR_MODULES}${lp_join}${NO_COL}${LP_MARK_MODULES_CLOSE}" + fi + else # No color. + _lp_join "${LP_MARK_MODULES_SEP}" "${lp_modules[@]}" + lp_modules_color="${LP_MARK_MODULES_OPEN}${lp_join}${LP_MARK_MODULES_CLOSE}" + fi + else + return "$?" + fi +} + + +# Same as bash '\l', but can be inlined as a constant as the value will not +# change during the shell's life. +_lp_terminal_device() { + lp_terminal_device="$(basename -- "$(tty)" 2>/dev/null)" +} + +# Determine what type of user we are +_lp_user() { + if (( EUID == 0 )); then + # user is root + return 2 + elif [[ "${USER-}" != "$(logname 2>/dev/null || printf '%s' "${LOGNAME-}")" ]]; then + # user is not login user + return 1 + else + return 0 + fi +} + +# Return the username (if we should display one). +_lp_username() { + if (( LP_USER_ALWAYS == -1 )); then + # No username ever + return 2 + elif (( LP_USER_ALWAYS )) || ! _lp_user; then + lp_username=${USER:-${USERNAME:-${LOGNAME-}}} + + if [[ -z $lp_username ]]; then + lp_username=$(id -nu 2>/dev/null) + fi + + local ret + __lp_escape "$lp_username" + lp_username=$ret + + return 0 + else + return 1 + fi +} + +_lp_username_color() { + _lp_username || return "$?" + + lp_username_color="${LP_COLOR_USER}${lp_username}${NO_COL}" +} + +# Test the code with the commands: +# sudo id # sudo, enter your credentials +# sudo -K # revoke your credentials +# sudo -v # return non-zero when no credentials are cached +# sudo -nvk # return true if user can run commands without password input +_lp_sudo_active() { + (( LP_ENABLE_SUDO )) || return 2 + (( _LP_SUDO_NOPASSWORD )) && return 0 + \sudo -nv 2>/dev/null || return 1 +} + +_lp_sudo_active_color() { + (( LP_ENABLE_SUDO )) || return 2 + + if _lp_sudo_active; then + lp_sudo_active_color=$LP_COLOR_MARK_SUDO + else + lp_sudo_active_color=$LP_COLOR_MARK_NO_SUDO + fi +} + +_lp_hostname() { + # Only process hostname elements if we haven't turned them off + if (( LP_HOSTNAME_ALWAYS != -1 )); then + _lp_connection + if [[ $lp_connection == lcl ]] && ! (( LP_HOSTNAME_ALWAYS )); then + # no hostname if local + return 1 + fi + + if [[ $LP_HOSTNAME_METHOD == fqdn ]]; then + lp_hostname=$(hostname -f 2>/dev/null) + elif [[ $LP_HOSTNAME_METHOD == pretty ]]; then + if [[ $LP_OS == Darwin ]]; then + lp_hostname=$(scutil --get ComputerName 2>/dev/null) + else + lp_hostname=$(hostnamectl --pretty 2>/dev/null) + fi + fi + + if [[ -z ${lp_hostname-} ]]; then + lp_hostname=${HOSTNAME:-${HOST-}} + fi + + # Truncate to the first subdomain + if [[ $LP_HOSTNAME_METHOD == short ]]; then + lp_hostname=${lp_hostname%%.*} + fi + + local ret + __lp_escape "$lp_hostname" + lp_hostname=$ret + + else + return 2 + fi +} + +# Put the hostname if not locally connected +# color it in cyan within SSH, and a warning red if within telnet +# else display the host without color +_lp_hostname_color() { + if _lp_connected_display; then + lp_hostname_color="${LP_COLOR_X11_ON}" + else + lp_hostname_color="${LP_COLOR_X11_OFF}" + fi + + if _lp_chroot; then + lp_hostname_color+="(${lp_chroot})" + fi + + if _lp_hostname; then + + case "$lp_connection" in + lcl) + lp_hostname_color+="@${LP_COLOR_HOST}${lp_hostname}${NO_COL}" + ;; + ssh) + local client_ip client_port server_ip server_port hostname= + # client_* are unused + # shellcheck disable=SC2034 + IFS=" " read -r client_ip client_port server_ip server_port <<<"$SSH_CONNECTION" + local username=${USER:-${USERNAME:-${LOGNAME-}}} + if _lp_create_link "ssh://${username}@${server_ip}:${server_port}" "$lp_hostname"; then + hostname="$lp_link" + else + hostname="$lp_hostname" + fi + # If we want a different color for each host + (( LP_ENABLE_SSH_COLORS )) && LP_COLOR_SSH="$LP_COLOR_HOST_HASH" + lp_hostname_color+="@${LP_COLOR_SSH}${hostname}${NO_COL}" + ;; + su) + lp_hostname_color+="@${LP_COLOR_SU}${lp_hostname}${NO_COL}" + ;; + tel) + lp_hostname_color+="@${LP_COLOR_TELNET}${lp_hostname}${NO_COL}" + ;; + *) + lp_hostname_color+="@${NO_COL}${lp_hostname}" # defaults to no color + ;; + esac + else + if [[ -n ${lp_chroot-} ]]; then + # End the color of the chroot + lp_hostname_color+=${NO_COL} + else + # Nothing to display + lp_hostname_color="" + return 1 + fi + fi +} + +_lp_dirstack() { + (( LP_ENABLE_DIRSTACK )) || return 2 + + local count dir_stack + dir_stack=$(dirs -p; printf x) + + __lp_line_count "${dir_stack%x}" + lp_dirstack=$count + + (( lp_dirstack > 1 )) +} + +_lp_dirstack_color() { + _lp_dirstack || return "$?" + + lp_dirstack_color="${LP_COLOR_DIRSTACK}${LP_MARK_DIRSTACK}${lp_dirstack}${NO_COL}" +} + +_lp_aws_profile() { + (( LP_ENABLE_AWS_PROFILE )) || return 2 + + local ret + + local aws_profile="${AWS_PROFILE-${AWS_DEFAULT_PROFILE-${AWS_VAULT-}}}" + if [[ -n $aws_profile ]]; then + __lp_escape "${aws_profile}" + lp_aws_profile=$ret + else + return 1 + fi +} + +_lp_aws_profile_color() { + unset lp_aws_profile_color + _lp_aws_profile || return "$?" + + lp_aws_profile_color="[${LP_COLOR_AWS_PROFILE}${lp_aws_profile}${NO_COL}]" +} + +_lp_shell_level() { + (( LP_ENABLE_SHLVL )) || return 2 + + lp_shell_level=${SHLVL:-1} + + (( lp_shell_level > 1 )) +} + +_lp_shell_level_color() { + _lp_shell_level || return "$?" + + lp_shell_level_color="${LP_COLOR_SHLVL}${LP_MARK_SHLVL}${lp_shell_level}${NO_COL}" +} + +################ +# Related jobs # +################ + +# Return the count of detached screen and/or tmux sessions. +_lp_detached_sessions() { + (( LP_ENABLE_DETACHED_SESSIONS )) || return 2 + + local -i count=0 + (( _LP_ENABLE_SCREEN )) && count=$(screen -ls 2> /dev/null | GREP_OPTIONS='' \grep -c '[Dd]etach[^)]*)$') + (( _LP_ENABLE_TMUX )) && count+=$(tmux list-sessions 2> /dev/null | GREP_OPTIONS='' \grep -cv 'attached') + lp_detached_sessions=$count + + (( lp_detached_sessions )) +} + +# Return the count of attached running shell jobs (started with $ myjob &) and/or +# stopped jobs (suspended with Ctrl-Z). +_lp_jobcount() { + (( LP_ENABLE_JOBS )) || return 2 + + local jobs + local -i count + # Count running jobs + # The $(...) syntax strips trailing newlines, so add a character to the end + # then remove it to prevent that. Otherwise 0 and 1 jobs look the same. + jobs="$(jobs -r; printf x)" + __lp_line_count "${jobs%x}" + lp_running_jobs=$count + + # Count stopped jobs + jobs="$(jobs -s; printf x)" + __lp_line_count "${jobs%x}" + lp_stopped_jobs=$count + + (( lp_running_jobs || lp_stopped_jobs )) +} + +# Display the count of detached sessions and shell jobs if not zero. +_lp_jobcount_color() { + (( LP_ENABLE_JOBS || LP_ENABLE_DETACHED_SESSIONS )) || return 2 + + lp_jobcount_color= + + _lp_detached_sessions && lp_jobcount_color="${LP_COLOR_JOB_D}${lp_detached_sessions}d${NO_COL}" + + if _lp_jobcount; then + if (( lp_running_jobs > 0 )); then + [[ -n "$lp_jobcount_color" ]] && lp_jobcount_color+="$LP_MARK_JOBS_SEPARATOR" + lp_jobcount_color+="${LP_COLOR_JOB_R}${lp_running_jobs}&${NO_COL}" + fi + if (( lp_stopped_jobs > 0 )); then + [[ -n "$lp_jobcount_color" ]] && lp_jobcount_color+="$LP_MARK_JOBS_SEPARATOR" + lp_jobcount_color+="${LP_COLOR_JOB_Z}${lp_stopped_jobs}z${NO_COL}" + fi + fi + + [[ -n "$lp_jobcount_color" ]] +} + +###################### +# VCS branch display # +###################### + +_lp_are_vcs_enabled() { + local _path + for _path in ${LP_DISABLED_VCS_PATHS[@]+"${LP_DISABLED_VCS_PATHS[@]}"}; do + if [[ -n "$_path" && "$PWD" == "$_path"* ]]; then + return 1 + fi + done + return 0 +} + +# Search upwards through a directory structure looking for a sign of a VCS +# repository. Used to avoid invoking VCS binaries to discover if in a repo. +_lp_find_vcs() { + if ! _lp_are_vcs_enabled; then + lp_vcs_type="disabled" + lp_vcs_root="" + return 2 + fi + + lp_vcs_type="" + + # Based on the Git behavior here: + # https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables + if [[ -d "${GIT_DIR-}" ]]; then + lp_vcs_type="git" + lp_vcs_dir="$GIT_DIR" + lp_vcs_root="${GIT_WORK_TREE:-"$(\git rev-parse --show-toplevel 2>/dev/null)"}" + fi + + if [[ -z $lp_vcs_type ]]; then + local vcs + lp_vcs_root="$PWD" + while [[ -n "$lp_vcs_root" ]]; do + for vcs in ${_LP_ENABLED_VCSS[@]+"${_LP_ENABLED_VCSS[@]}"}; do + lp_vcs_dir="$lp_vcs_root/.$vcs" + if [[ -d "$lp_vcs_dir" ]]; then + lp_vcs_type="$vcs" + break 2 + fi + done + unset lp_vcs_dir + + if (( LP_ENABLE_GIT )) && [[ -f "$lp_vcs_root/.git" ]]; then + lp_vcs_type="git" + lp_vcs_dir="$(\git rev-parse --git-dir 2>/dev/null)" + break + fi + + if (( LP_ENABLE_FOSSIL )) && [[ -f "$lp_vcs_root/_FOSSIL_" || -f "$lp_vcs_root/.fslckout" ]]; then + lp_vcs_type="fossil" + return 0 + fi + + lp_vcs_root="${lp_vcs_root%/*}" + done + fi + + if [[ $lp_vcs_type == git ]]; then + if [[ -n "${VCSH_DIRECTORY-}" ]]; then + lp_vcs_subtype="vcsh" + elif [[ -d "${lp_vcs_dir}/svn" ]]; then + lp_vcs_subtype="svn" + else + lp_vcs_subtype="" + fi + fi + + if [[ -z $lp_vcs_type ]]; then + lp_vcs_root="" + return 1 + fi +} + +# Set the prompt mark depending on the current VCS. +# shellcheck disable=SC2120 +_lp_smart_mark() { + local subtype="${2:-"${lp_vcs_subtype-}"}" + case "${1:-$lp_vcs_type}" in + git) + if [[ $subtype == vcsh ]]; then + lp_smart_mark="$LP_MARK_VCSH${VCSH_DIRECTORY-}$LP_MARK_GIT$LP_MARK_VCSH" + elif [[ $subtype == svn ]]; then + lp_smart_mark="$LP_MARK_GIT$LP_MARK_SVN" + else + lp_smart_mark="$LP_MARK_GIT" + fi + ;; + hg) lp_smart_mark="$LP_MARK_HG" ;; + svn) lp_smart_mark="$LP_MARK_SVN" ;; + fossil) lp_smart_mark="$LP_MARK_FOSSIL" ;; + bzr) lp_smart_mark="$LP_MARK_BZR" ;; + disabled) lp_smart_mark="$LP_MARK_DISABLED" ;; + *) lp_smart_mark="$LP_MARK_DEFAULT" ;; + esac +} + +# GENERIC VCS # + +# Create a formatted string describing the status of the repo. +_lp_vcs_details_color() { + local branch + if _lp_vcs_branch; then + branch="$lp_vcs_branch" + + if _lp_vcs_bookmark; then + branch+=": $lp_vcs_bookmark" + fi + elif _lp_vcs_bookmark; then + branch="$lp_vcs_bookmark" + elif _lp_vcs_tag; then + branch="tag: $lp_vcs_tag" + else + _lp_vcs_commit_id + branch="${lp_vcs_commit_id:0:7}" + fi + + lp_vcs_details_color="$LP_COLOR_UP" + + local has_commit= + if _lp_vcs_commits_off_remote; then + lp_vcs_details_color="$LP_COLOR_COMMITS_BEHIND" + if [[ "$lp_vcs_commit_ahead" -ne "0" && "$lp_vcs_commit_behind" -ne "0" ]]; then + has_commit="${LP_COLOR_COMMITS}+$lp_vcs_commit_ahead${NO_COL}/${LP_COLOR_COMMITS_BEHIND}-$lp_vcs_commit_behind${NO_COL}" + elif [[ "$lp_vcs_commit_ahead" -ne "0" ]]; then + has_commit="${LP_COLOR_COMMITS}$lp_vcs_commit_ahead${NO_COL}" + lp_vcs_details_color="$LP_COLOR_COMMITS" + elif [[ "$lp_vcs_commit_behind" -ne "0" ]]; then + has_commit="${LP_COLOR_COMMITS_BEHIND}-$lp_vcs_commit_behind${NO_COL}" + fi + fi + + local ret has_lines= + if _lp_vcs_uncommitted_files; then + _lp_vcs_unstaged_lines; ret=$? + # Only show unstaged changes if the VCS supports staging, otherwise + # show uncommitted changes + if (( ret == 0 )); then + has_lines="+$lp_vcs_unstaged_i_lines/-$lp_vcs_unstaged_d_lines" + elif (( ret == 1 )); then + has_lines="+0/-0" + else + _lp_vcs_uncommitted_lines + has_lines="+$lp_vcs_uncommitted_i_lines/-$lp_vcs_uncommitted_d_lines" + fi + lp_vcs_details_color="$LP_COLOR_CHANGES" + fi + + lp_vcs_details_color+="$branch" + if [[ -n "$has_lines" || -n "$has_commit" ]]; then + lp_vcs_details_color+="${NO_COL}(" + if [[ -n "$has_lines" ]]; then + lp_vcs_details_color+="${LP_COLOR_DIFF}${has_lines}${NO_COL}${has_commit:+,}" + fi + lp_vcs_details_color+="${has_commit})" + fi + + if _lp_vcs_stash_count; then + lp_vcs_details_color+="$LP_COLOR_COMMITS$LP_MARK_STASH" + fi + + if _lp_vcs_untracked_files; then + lp_vcs_details_color+="$LP_COLOR_CHANGES$LP_MARK_UNTRACKED" + fi + + if _lp_vcs_head_status; then + lp_vcs_details_color+=" $LP_COLOR_CHANGES$lp_vcs_head_status" + if [[ -n "${lp_vcs_head_details-}" ]]; then + lp_vcs_details_color+="(${lp_vcs_head_details})" + fi + fi + + lp_vcs_details_color+="$NO_COL" +} + +# Check if the detected VCS is enabled in Liquid Prompt and the current +# directory is a valid repository of that type. +_lp_vcs_active() { + "_lp_${lp_vcs_type}_active" +} + +# Get the branch name of the repo in the current directory. +_lp_vcs_branch() { + "_lp_${lp_vcs_type}_branch" +} + +# Get the bookmark name of the repo in the current directory. +_lp_vcs_bookmark() { + "_lp_${lp_vcs_type}_bookmark" +} + +# Get a tag name of the repo in the current directory. +_lp_vcs_tag() { + "_lp_${lp_vcs_type}_tag" +} + +# Get the current commit string for the repo in the current directory. +_lp_vcs_commit_id() { + "_lp_${lp_vcs_type}_commit_id" +} + +# Get additional information if the repo is in a special or unusual state. +_lp_vcs_head_status() { + "_lp_${lp_vcs_type}_head_status" + # TODO: set lp_vcs_head_details if not set? +} + +# Get the number of stashes in the repo. +_lp_vcs_stash_count() { + "_lp_${lp_vcs_type}_stash_count" +} + +# Get the number of commits ahead and behind the upstream branch. +_lp_vcs_commits_off_remote() { + "_lp_${lp_vcs_type}_commits_off_remote" +} + +# Get the number of untracked aka extra files in the repo. +_lp_vcs_untracked_files() { + "_lp_${lp_vcs_type}_untracked_files" +} + +# Get the number of changed files compared to the last or checked out commit. +_lp_vcs_uncommitted_files() { + "_lp_${lp_vcs_type}_uncommitted_files" +} + +# Get the number of changed lines compared to the last or checked out commit. +_lp_vcs_uncommitted_lines() { + "_lp_${lp_vcs_type}_uncommitted_lines" +} + +# Get the number of changed files compared to staging. +_lp_vcs_unstaged_files() { + "_lp_${lp_vcs_type}_unstaged_files" +} + +# Get the number of changed lines compared to staging. +_lp_vcs_unstaged_lines() { + "_lp_${lp_vcs_type}_unstaged_lines" +} + +# Get the number of changed files in staging compared to the last or checked out commit. +_lp_vcs_staged_files() { + "_lp_${lp_vcs_type}_staged_files" +} + +# Get the number of changed lines in staging compared to the last or checked out commit. +_lp_vcs_staged_lines() { + "_lp_${lp_vcs_type}_staged_lines" +} + +# GIT # + +# Check if Git is enabled in Liquid Prompt and the current directory is a valid +# Git repository. +_lp_git_active() { + (( LP_ENABLE_GIT )) || return 2 + \git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 1 +} + +# Get the branch name of the Git repo in the current directory. +_lp_git_branch() { + local branch ret + # Recent versions of Git support the --short option for symbolic-ref, but + # not 1.7.9 (Ubuntu 12.04) + if branch="$(\git symbolic-ref -q HEAD 2>/dev/null)"; then + __lp_escape "${branch#refs/heads/}" + lp_vcs_branch="$ret" + else + return 1 + fi +} + +# Git does not support bookmarks. +_lp_git_bookmark() { return 2 ; } + +# Get a tag name of the Git repo in the current directory. +_lp_git_tag() { + local tag ret + if tag="$(\git describe --tags --exact-match 2>/dev/null)"; then + __lp_escape "$tag" + lp_vcs_tag="$ret" + else + return 1 + fi +} + +# Get the current full commit hash of the repo in the current directory. +_lp_git_commit_id() { + lp_vcs_commit_id="$(\git rev-parse -q HEAD 2>/dev/null)" +} + +# Get additional information if HEAD is in merging, rebasing or cherry-picking state. +_lp_git_head_status() { + local IFS="" step total + + if [[ -f "${lp_vcs_dir}/MERGE_HEAD" ]]; then + lp_vcs_head_status="MERGING" + elif [[ -d "${lp_vcs_dir}/rebase-merge" ]]; then + test -r "${lp_vcs_dir}/rebase-merge/msgnum" && read -r step <"${lp_vcs_dir}/rebase-merge/msgnum" + test -r "${lp_vcs_dir}/rebase-merge/end" && read -r total <"${lp_vcs_dir}/rebase-merge/end" + if [[ -f "${lp_vcs_dir}/rebase-merge/interactive" ]]; then + lp_vcs_head_status="REBASE-i" + else + lp_vcs_head_status="REBASE-m" + fi + elif [[ -d "${lp_vcs_dir}/rebase-apply" ]]; then + test -r "${lp_vcs_dir}/rebase-apply/next" && read -r step <"${lp_vcs_dir}/rebase-apply/next" + test -r "${lp_vcs_dir}/rebase-apply/last" && read -r total <"${lp_vcs_dir}/rebase-apply/last" + if [[ -f "${lp_vcs_dir}/rebase-apply/rebasing" ]]; then + lp_vcs_head_status="REBASE" + elif [[ -f "${lp_vcs_dir}/rebase-apply/applying" ]]; then + lp_vcs_head_status="AM" + else + lp_vcs_head_status="AM/REBASE" + fi + elif [[ -f "${lp_vcs_dir}/CHERRY_PICK_HEAD" ]]; then + lp_vcs_head_status="CHERRY-PICKING" + elif [[ -f "${lp_vcs_dir}/REVERT_HEAD" ]]; then + lp_vcs_head_status="REVERTING" + elif [[ -f "${lp_vcs_dir}/BISECT_START" ]]; then + lp_vcs_head_status="BISECTING" + else + return 1 + fi + + if [[ -n "$step" && -n "$total" ]]; then + lp_vcs_head_details="${step}/${total}" + else + lp_vcs_head_details="" + fi +} + +# Get the number of Git stashes in the repo. +_lp_git_stash_count() { + lp_vcs_stash_count="$(\git rev-list --walk-reflogs --count refs/stash 2>/dev/null)" + (( lp_vcs_stash_count )) +} + +# Count commits behind and ahead on the remote tracking branch of the current +# local branch. +_lp_git_commits_off_remote() { + local counts + # The "@{upstream}" notation was added in Git 1.7.0, so this should work for everyone + counts="$(\git rev-list --count --left-right '@{upstream}...HEAD' 2>/dev/null)" || return 2 + IFS=$' \t' read -r lp_vcs_commit_behind lp_vcs_commit_ahead <<<"$counts" + (( lp_vcs_commit_behind || lp_vcs_commit_ahead )) +} + +# Get the number of untracked files in the repo. +_lp_git_untracked_files() { + lp_vcs_untracked_files="$(LC_ALL=C \git status --porcelain 2>/dev/null | GREP_OPTIONS='' \grep -c '^??')" + (( lp_vcs_untracked_files )) +} + +# Get the number of changed files. +__lp_git_diff_shortstat_files() { # diff_shortstat + local stat="$1" + + if [[ "$stat" = *changed* ]]; then + stat="${stat/ file*}" + lp_git_diff_shortstat_files=${stat//[$' \t']} + else + return 1 + fi +} + +# Get the number of changed lines. +__lp_git_diff_shortstat_lines() { # diff_shortstat + local stat="$1" + + stat=${stat/*changed, /} # removing "n file(s) changed" + + if [[ "$stat" = *insertion* ]]; then + lp_git_diff_shortstat_i_lines=${stat/ inser*} + else + lp_git_diff_shortstat_i_lines=0 + fi + + if [[ "$stat" = *deletion* ]]; then + stat=${stat/*\(+\), } + lp_git_diff_shortstat_d_lines=${stat/ del*/} + else + lp_git_diff_shortstat_d_lines=0 + fi + + (( lp_git_diff_shortstat_i_lines || lp_git_diff_shortstat_d_lines )) +} + +__lp_git_diff_shortstat_uncommitted() { + if [[ -z ${_lp_git_diff_shortstat_uncommitted-} ]]; then + _lp_git_diff_shortstat_uncommitted="$(LC_ALL=C \git diff --shortstat HEAD -- 2>/dev/null)" + fi +} + +# Get the number of changed files compared to HEAD. +_lp_git_uncommitted_files() { + __lp_git_diff_shortstat_uncommitted + + local lp_git_diff_shortstat_files + __lp_git_diff_shortstat_files "$_lp_git_diff_shortstat_uncommitted" || return "$?" + + lp_vcs_uncommitted_files=$lp_git_diff_shortstat_files +} + +# Get the number of changed lines compared to HEAD. +_lp_git_uncommitted_lines() { + __lp_git_diff_shortstat_uncommitted + + local lp_git_diff_shortstat_i_lines lp_git_diff_shortstat_d_lines + __lp_git_diff_shortstat_lines "$_lp_git_diff_shortstat_uncommitted" || return "$?" + + lp_vcs_uncommitted_i_lines=$lp_git_diff_shortstat_i_lines + lp_vcs_uncommitted_d_lines=$lp_git_diff_shortstat_d_lines +} + +__lp_git_diff_shortstat_unstaged() { + if [[ -z ${_lp_git_diff_shortstat_unstaged-} ]]; then + _lp_git_diff_shortstat_unstaged="$(LC_ALL=C \git diff --shortstat 2>/dev/null)" + fi +} + +# Get the number of changed files compared to staging. +_lp_git_unstaged_files() { + __lp_git_diff_shortstat_unstaged + + local lp_git_diff_shortstat_files + __lp_git_diff_shortstat_files "$_lp_git_diff_shortstat_unstaged" || return "$?" + + # shellcheck disable=SC2034 + lp_vcs_unstaged_files=$lp_git_diff_shortstat_files +} + +# Get the number of changed lines compared to staging. +_lp_git_unstaged_lines() { + __lp_git_diff_shortstat_unstaged + + local lp_git_diff_shortstat_i_lines lp_git_diff_shortstat_d_lines + __lp_git_diff_shortstat_lines "$_lp_git_diff_shortstat_unstaged" || return "$?" + + lp_vcs_unstaged_i_lines=$lp_git_diff_shortstat_i_lines + lp_vcs_unstaged_d_lines=$lp_git_diff_shortstat_d_lines +} + +__lp_git_diff_shortstat_staged() { + if [[ -z ${_lp_git_diff_shortstat_staged-} ]]; then + _lp_git_diff_shortstat_staged="$(LC_ALL=C \git diff --shortstat --cached 2>/dev/null)" + fi +} + +# Get the number of changed files in staging compared to HEAD. +_lp_git_staged_files() { + __lp_git_diff_shortstat_staged + + local lp_git_diff_shortstat_files + __lp_git_diff_shortstat_files "$_lp_git_diff_shortstat_staged" || return "$?" + + # shellcheck disable=SC2034 + lp_vcs_staged_files=$lp_git_diff_shortstat_files +} + +# Get the number of changed lines in staging compared to HEAD. +# shellcheck disable=SC2034 +_lp_git_staged_lines() { + __lp_git_diff_shortstat_staged + + local lp_git_diff_shortstat_i_lines lp_git_diff_shortstat_d_lines + __lp_git_diff_shortstat_lines "$_lp_git_diff_shortstat_staged" || return "$?" + + lp_vcs_staged_i_lines=$lp_git_diff_shortstat_i_lines + lp_vcs_staged_d_lines=$lp_git_diff_shortstat_d_lines +} + +# MERCURIAL # + +# Check if Mercurial is enabled in Liquid Prompt and the current directory is a +# valid Mercurial repository. +_lp_hg_active() { + (( LP_ENABLE_HG )) || return 2 + "$LP_HG_COMMAND" root >/dev/null 2>&1 || return 1 +} + +# Get the branch name of the Mercurial repo in the current directory. +_lp_hg_branch() { + local branch ret + if branch="$("$LP_HG_COMMAND" branch 2>/dev/null)"; then + __lp_escape "$branch" + lp_vcs_branch="$ret" + else + # This should never happen. Should this function return a branch name + # only if the head of the branch is checked out? But there can be + # multiple heads of a branch... + return 1 + fi +} + +# Get the bookmark name of the Mercurial repo in the current directory. +_lp_hg_bookmark() { + local bookmark ret + if bookmark="$("$LP_HG_COMMAND" bookmark --list --quiet . 2>/dev/null)"; then + __lp_escape "$bookmark" + lp_vcs_bookmark="$ret" + else + return 1 + fi +} + +# Get the most recent tag that refers to the current revision. +_lp_hg_tag() { + local tags ret + tags="$("$LP_HG_COMMAND" identify --template='{tags}' 2>/dev/null)" + if [[ -n "$tags" ]]; then + # Tags are separated by ':', get the first one + __lp_escape "${tags%%:*}" + lp_vcs_tag="$ret" + else + return 1 + fi +} + +# Get the current global revision id for the repo in the current directory. +_lp_hg_commit_id() { + lp_vcs_commit_id="$("$LP_HG_COMMAND" identify --id 2>/dev/null)" +} + +# Get additional information if the repo is in any unfinished state. +_lp_hg_head_status() { + if [[ -d "${lp_vcs_dir}/merge" ]]; then + lp_vcs_head_status="MERGING" + elif [[ -f "${lp_vcs_dir}/rebasestate" ]]; then + lp_vcs_head_status="REBASING" + elif [[ -f "${lp_vcs_dir}/updatestate" ]]; then + lp_vcs_head_status="UPDATING" + elif [[ -f "${lp_vcs_dir}/bisect.state" ]]; then + lp_vcs_head_status="BISECTING" + elif [[ -f "${lp_vcs_dir}/shelvedstate" ]]; then + lp_vcs_head_status="SHELVING" + elif [[ -f "${lp_vcs_dir}/graftstate" ]]; then + lp_vcs_head_status="GRAFTING" + else + return 1 + fi +} + +# Get the number of Mercurial shelves in the repo. +_lp_hg_stash_count() { + local shelves count + shelves="$("$LP_HG_COMMAND" shelve --list 2>/dev/null; printf x)" + __lp_line_count "${shelves%x}" + lp_vcs_stash_count="$count" + (( lp_vcs_stash_count )) +} + +# https://github.com/nojhan/liquidprompt/issues/217 +# return: always false (2: disabled). +_lp_hg_commits_off_remote() { + #commits=$("$LP_HG_COMMAND" outgoing --no-merges 2>/dev/null | GREP_OPTIONS='' \grep -c '\(^changeset\:\)') + return 2 +} + +# Get the number of untracked files in the Mercurial repo. +_lp_hg_untracked_files() { + local untracked + untracked="$("$LP_HG_COMMAND" status --unknown --template '{status}' 2>/dev/null)" + lp_vcs_untracked_files="${#untracked}" + (( lp_vcs_untracked_files )) +} + +# Get the number of changed files compared to the base revision. +_lp_hg_uncommitted_files() { + local files + files="$("$LP_HG_COMMAND" status --modified --template '{status}' 2>/dev/null)" + lp_vcs_uncommitted_files="${#files}" + (( lp_vcs_uncommitted_files )) +} + +# Get the number of changed lines compared to the base revision. +_lp_hg_uncommitted_lines() { + IFS=' ' read -r lp_vcs_uncommitted_i_lines lp_vcs_uncommitted_d_lines \ + <<<"$("$LP_HG_COMMAND" diff --stat 2>/dev/null | sed -n '$ s/^.*, \([0-9]*\) .*, \([0-9]*\).*$/\1 \2/p')" + + (( lp_vcs_uncommitted_i_lines || lp_vcs_uncommitted_d_lines )) +} + +# Mercurial does not support a staging area. +_lp_hg_unstaged_files() { return 2 ; } +_lp_hg_unstaged_lines() { return 2 ; } +_lp_hg_staged_files() { return 2 ; } +_lp_hg_staged_lines() { return 2 ; } + +# SUBVERSION # + +# Check if Subversion is enabled in Liquid Prompt and the current directory is a +# valid Subversion repository. +_lp_svn_active() { + (( LP_ENABLE_SVN )) || return 2 + \svn info >/dev/null 2>&1 || return 1 +} + +# Get the branch name of the repo in the current directory. +_lp_svn_branch() { + local ret url + # SVN info shows repository-relative URLs since v1.8 + url="$(LC_ALL=C \svn info 2>/dev/null)" + url="${url#*Relative URL: }" + url="${url%%$'\n'*}" + [[ -z "$url" ]] && return 1 + + if [[ "$url" == */trunk* ]]; then + lp_vcs_branch=trunk + elif [[ "$url" == */branches/?* ]]; then + url="${url##*/branches/}" + __lp_escape "${url%/*}" + lp_vcs_branch="$ret" + elif [[ "$url" == */tags/?* ]]; then + url="${url##*/tags/}" + __lp_escape "${url%/*}" + lp_vcs_branch="tag/$ret" + else + return 1 + fi +} + +# Subversion does not support bookmarks. +_lp_svn_bookmark() { return 2 ; } + +# Subversion does not support tags. What are generally agreed upon as +# being tags are internally branches. These are returned by _lp_svn_branch(). +_lp_svn_tag() { return 2 ; } + +# Get the current revision number for the repo in the current directory. +_lp_svn_commit_id() { + lp_vcs_commit_id="$(\svn info --show-item revision 2>/dev/null)" +} + +# Subversion does not have extra head statuses. A Subversion merge is no different +# than a manual file change, so the repository has no extra state to track. +_lp_svn_head_status() { return 2 ; } + +# Subversion does not support stashes. +_lp_svn_stash_count() { return 2 ; } + +# Subversion does not support remote tracking branches (as it is not a +# distributed version control system). +_lp_svn_commits_off_remote() { return 2 ; } + +# Get the number of untracked files in the Subversion repo. +_lp_svn_untracked_files() { + lp_vcs_untracked_files="$(LC_ALL=C \svn status 2>/dev/null | GREP_OPTIONS='' \grep -c '^?')" + (( lp_vcs_untracked_files )) +} + +# Get the number of changed files compared to the base revision. +_lp_svn_uncommitted_files() { + local files count + # svn status is unsafe with newline chars in filenames, which will throw + # off this count + files="$(\svn status --quiet 2>/dev/null; printf x)" + __lp_line_count "${files%x}" + lp_vcs_uncommitted_files="$count" + (( lp_vcs_uncommitted_files )) +} + +# Get the number of changed lines compared to the base revision. +_lp_svn_uncommitted_lines() { + IFS=' ' read -r lp_vcs_uncommitted_i_lines lp_vcs_uncommitted_d_lines \ + <<<"$(\svn diff --internal-diff 2>/dev/null | awk ' + BEGIN { plus=0; minus=0 } + /^(\+[^+])|(\+$)/ { plus+=1 } + /^(-[^-])|(-$)/ { minus+=1 } + END { + print plus" "minus + }')" + + (( lp_vcs_uncommitted_i_lines || lp_vcs_uncommitted_d_lines )) +} + +# Subversion does not support a staging area. +_lp_svn_unstaged_files() { return 2 ; } +_lp_svn_unstaged_lines() { return 2 ; } +_lp_svn_staged_files() { return 2 ; } +_lp_svn_staged_lines() { return 2 ; } + +# FOSSIL # + +# Check if Fossil is enabled in Liquid Prompt and the current directory is a +# valid Fossil repository. +_lp_fossil_active() { + (( LP_ENABLE_FOSSIL )) || return 2 + \fossil status >/dev/null 2>&1 || return 1 +} + +# Get the branch name of the repo in the current directory. +_lp_fossil_branch() { + local branch ret + + # branch current command added in fossil 2.7 + if ! branch="$(\fossil branch current 2>/dev/null)"; then + # Almost any character can be in a branch name, but we have no way of + # knowing if a newline is part of the name or not. In fact, there is no + # way to prevent a branch containing the string '\n* ' to not break + # this. Just hope that no one crazy enough to do that to their branch + # names is running Fossil <2.7 + branch="$(\fossil branch list 2>/dev/null)" + branch="${branch#*$'\n\* '}" + # If the current branch is the first in the list, the above check would + # not have removed anything + branch="${branch#\* }" + branch="${branch%%$'\n'*}" + fi + + if [[ -n "$branch" ]]; then + __lp_escape "$branch" + lp_vcs_branch="$ret" + else + return 1 + fi +} + +# Fossil does not support bookmarks. +_lp_fossil_bookmark() { return 2 ; } + +# Fossil does not support unique tags. Fossil tags can refer to multiple checkin IDs, +# so a matching tag is not a useful unique ID. +_lp_fossil_tag() { return 2 ; } + +# Get the current full commit hash of the Fossil repo in the current directory. +_lp_fossil_commit_id() { + lp_vcs_commit_id="$(LC_ALL=C \fossil status 2>/dev/null | sed -n 's/^checkout:[[:space:]]*\([^[:space:]]*\).*/\1/p')" +} + +# Get additional information if the check-out is in merging before a commit. +_lp_fossil_head_status() { + local option + option="$(LC_ALL=C \fossil undo --dry-run 2>/dev/null)" + + if [[ "$option" == *"fossil merge"* ]]; then + lp_vcs_head_status="MERGING" + else + return 1 + fi +} + +# Get the number of Fossil stashes in the repo. +_lp_fossil_stash_count() { + local stashes count + stashes="$(\fossil stash list 2>/dev/null; printf x)" + __lp_line_count "${stashes%x}" + # Each stash takes up two lines, and no stashes is one line + lp_vcs_stash_count=$(( count / 2 )) + (( lp_vcs_stash_count )) +} + +# Fossil does not support remote tracking branches. Fossil by default keeps the local +# repository in sync with the remote. Even if a user disables that, it is not possible +# to have a local and remote branch named the same not in sync. +_lp_fossil_commits_off_remote() { return 2 ; } + +# Get the number of extra files in the Fossil repo. +_lp_fossil_untracked_files() { + local extras count + extras="$(\fossil extras 2>/dev/null; printf x)" + __lp_line_count "${extras%x}" + lp_vcs_untracked_files=$count + (( lp_vcs_untracked_files )) +} + +# Get the number of changed files compared to the checked-out version. +_lp_fossil_uncommitted_files() { + local files + files="$(\fossil changes 2>/dev/null; printf x)" + __lp_line_count "${files%x}" + lp_vcs_uncommitted_files=$count + (( lp_vcs_uncommitted_files )) +} + +# Get the number of changed lines compared to the checked-out version. +_lp_fossil_uncommitted_lines() { + IFS=' ' read -r lp_vcs_uncommitted_i_lines lp_vcs_uncommitted_d_lines \ + <<<"$(\fossil diff --internal --verbose 2>/dev/null | awk ' + BEGIN { plus=0; minus=0 } + /^(\+[^+])|(\+$)/ { plus+=1 } + /^(-[^-])|(-$)/ { minus+=1 } + END { + print plus" "minus + }')" + + (( lp_vcs_uncommitted_i_lines || lp_vcs_uncommitted_d_lines )) +} + +# Fossil does not support a staging area. +_lp_fossil_unstaged_files() { return 2 ; } +_lp_fossil_unstaged_lines() { return 2 ; } +_lp_fossil_staged_files() { return 2 ; } +_lp_fossil_staged_lines() { return 2 ; } + +# Bazaar # + +# Check if Bazaar is enabled in Liquid Prompt and the current directory is a +# valid Bazaar repository. This check should be done before running any other +# _lp_bzr_* data functions. +_lp_bzr_active() { + (( LP_ENABLE_BZR )) || return 2 + \bzr status >/dev/null 2>&1 || return 1 +} + +# Get the branch name of the current directory +_lp_bzr_branch() { + local branch ret + if branch="$(\bzr nick 2> /dev/null)"; then + __lp_escape "$branch" + lp_vcs_branch="$ret" + else + return 1 + fi +} + +# Bazaar does not support bookmarks. A nick is somewhat like a bookmark, but there is +# no command to view a naked branch name, so the nick command is used for branches. +_lp_bzr_bookmark() { return 2 ; } + +# Get the most recent tag that refers to the current revision. +_lp_bzr_tag() { + local tag ret _ + IFS=$' \t' read -r tag _ <<<"$(LC_ALL=C \bzr tags --revision=last:1 2>/dev/null)" + if [[ -n "$tag" ]]; then + __lp_escape "$tag" + lp_vcs_tag="$ret" + else + return 1 + fi +} + +# Get the current full commit hash of the repo in the current directory. +_lp_bzr_commit_id() { + lp_vcs_commit_id="$(\bzr revno 2>/dev/null)" +} + +# Bazaar does not have extra head statuses. A Bazaar merge can be partially complete, +# but there is no command to test for it. +_lp_bzr_head_status() { return 2 ; } + +# Get the number of Bazaar shelves in the repo. +_lp_bzr_stash_count() { + local shelves count + shelves="$(\bzr shelve --list 2>/dev/null)" + local -i ret="$?" + + if (( ret == 0 )); then + # No error code means no shelves. + lp_vcs_stash_count=0 + elif (( ret == 1 )); then + # Return of 1 means there are shelves. + # The usual "printf x" trick can't be used, as it squashes the error code. + __lp_line_count "$shelves" + lp_vcs_stash_count=$(( count + 1 )) + else + return 1 + fi + + (( lp_vcs_stash_count )) +} + +# Bazaar does not support getting details of remote tracking branches. Bazaar does not +# keep a local copy of the remote state, so checking this would be impossible anyway. +_lp_bzr_commits_off_remote() { return 2 ; } + +# Get the number of unknown files in the repo. +_lp_bzr_untracked_files() { + lp_vcs_untracked_files="$(LC_ALL=C \bzr status --short 2>/dev/null | GREP_OPTIONS='' \grep -c '^?')" + + (( lp_vcs_untracked_files )) +} + +# Get the number of changed files compared to the checked-out version. +_lp_bzr_uncommitted_files() { + lp_vcs_uncommitted_files="$(LC_ALL=C \bzr status --short 2>/dev/null | GREP_OPTIONS='' \grep -vc '^?')" + + (( lp_vcs_uncommitted_files )) +} + +# Get the number of changed lines compared to the checked-out version. +_lp_bzr_uncommitted_lines() { + IFS=' ' read -r lp_vcs_uncommitted_i_lines lp_vcs_uncommitted_d_lines \ + <<<"$(\bzr diff 2>/dev/null | awk ' + BEGIN { plus=0; minus=0 } + /^(\+[^+])|(\+$)/ { plus+=1 } + /^(-[^-])|(-$)/ { minus+=1 } + END { + print plus" "minus + }')" + + (( lp_vcs_uncommitted_i_lines || lp_vcs_uncommitted_d_lines )) +} + +# Bazaar does not support a staging area. +_lp_fossil_unstaged_files() { return 2 ; } +_lp_fossil_unstaged_lines() { return 2 ; } +_lp_fossil_staged_files() { return 2 ; } +_lp_fossil_staged_lines() { return 2 ; } + +#################### +# Wifi link status # +#################### + +_lp_wifi_signal_strength() { + (( LP_ENABLE_WIFI_STRENGTH )) || return 2 + + local level + + __lp_wifi_signal_strength_raw || return "$?" + + if (( level > 110 && level < 256 )); then + # assume old-style WEXT 8-bit unsigned signal level. + (( level -= 256 )) # subtract 256 to convert to dBm. + fi + + # normalize to 0. + (( level > -40 )) && (( level = -40 )) + (( level < -100 )) && (( level = -100 )) + (( lp_wifi_signal_strength = 100 - (100 * ((level + 40) * -1 ) / 60 ) )) + + (( lp_wifi_signal_strength < LP_WIFI_STRENGTH_THRESHOLD )) +} + +case "$LP_OS" in + Linux) + __lp_wifi_signal_strength_raw() { + [[ -r "$_LP_LINUX_WIRELESS_FILE" ]] || return 2 + + unset lp_wifi_signal_strength + + local strength _ + while IFS=$' \t' read -r _ _ _ strength _; do + strength="${strength%%[![:digit:]-]*}" + + if [[ -n $strength ]] && ( [[ -z ${lp_wifi_signal_strength-} ]] \ + || (( strength < level )) ); then + level="$strength" + fi + done <"$_LP_LINUX_WIRELESS_FILE" + + [[ -n ${level-} ]] || return 2 + } + ;; + Darwin) + __lp_wifi_signal_strength_raw() { + level="$("$_LP_AIRPORT_BIN" --getinfo 2>/dev/null)" || return 2 + + level="${level#*"agrCtlRSSI: "}" + level="${level%%[![:digit:]-]*}" + + [[ -n $level ]] || return 2 + } + ;; + *) + _lp_wifi_signal_strength() { + return 2 + } + ;; +esac + +_lp_wifi_signal_strength_color() { + _lp_wifi_signal_strength || return "$?" + + local ret + _lp_color_map "$(( 100 - lp_wifi_signal_strength ))" + lp_wifi_signal_strength_color="${ret}${LP_MARK_WIFI}" + + if (( LP_PERCENTS_ALWAYS )); then + lp_wifi_signal_strength_color+="${lp_wifi_signal_strength}" + fi + lp_wifi_signal_strength_color+="${NO_COL}" +} + +################## +# Battery status # +################## + +# Get the battery status in percent. +case "$LP_OS" in + Linux) + __lp_battery_sysfs() { + if (( _LP_SHELL_zsh )); then + setopt local_options nullglob + fi + + local power_supply + for power_supply in "${_LP_LINUX_POWERSUPPLY_PATH}/"*; do + if ! [[ -r "${power_supply}/type" && -r "${power_supply}/present" && \ + -r "${power_supply}/status" && -r "${power_supply}/capacity" ]]; then + continue + fi + + local power_supply_type power_supply_present power_supply_scope + IFS= read -r power_supply_type <"${power_supply}/type" 2>/dev/null || continue + [[ $power_supply_type == 'Battery' ]] || continue + IFS= read -r power_supply_present <"${power_supply}/present" 2>/dev/null || continue + [[ $power_supply_present == '1' ]] || continue + + # Scope is a property of a power supply + # Scope = System or missing - power supply powers the system + # Scope = Device - power supply powers a device + if [[ -r "${power_supply}/scope" ]]; then + IFS= read -r power_supply_scope <"${power_supply}/scope" 2>/dev/null || continue + [[ $power_supply_scope == 'System' ]] || continue + fi + + IFS= read -r lp_battery_status <"${power_supply}/status" 2>/dev/null || continue + IFS= read -r lp_battery <"${power_supply}/capacity" 2>/dev/null || continue + return 0 + done + return 1 + } + + __lp_battery_acpi() { + local acpi + acpi="$(acpi --battery 2>/dev/null)" + + # Extract the battery load value in percent + # First, remove the beginning of the line... + lp_battery="${acpi#Battery *, }" + lp_battery="${lp_battery%%%*}" # remove everything starting at '%' + lp_battery_status="${acpi}" + } + + __lp_battery_detect() { + local lp_battery lp_battery_status + unset _LP_BATTERY_FUNCTION + + # First check SYSFS way. + _LP_BATTERY_FUNCTION=__lp_battery_sysfs + "$_LP_BATTERY_FUNCTION" 2>/dev/null && \ + [[ -n "${lp_battery-}" ]] && return 0 + + # Try with ACPI. + if command -v apci >/dev/null; then + _LP_BATTERY_FUNCTION=__lp_battery_acpi + "$_LP_BATTERY_FUNCTION" 2>/dev/null && \ + [[ -n "${lp_battery-}" ]] && return 0 + fi + + unset _LP_BATTERY_FUNCTION + return 1 + } + + _lp_battery() { + (( LP_ENABLE_BATT )) || return 5 + + unset lp_battery lp_battery_status + "$_LP_BATTERY_FUNCTION" + + if [[ -z "${lp_battery-}" ]]; then + # no battery level found + return 4 + fi + # discharging + if [[ "$lp_battery_status" == *"Discharging"* ]]; then + # under => 0, above => 1 + return "$(( lp_battery > LP_BATTERY_THRESHOLD ))" + # not charging + elif [[ "$lp_battery_status" == *"Not charging"* ]]; then + return 4 + # charging + else + # under => 2, above => 3 + return "$(( 2 + ( lp_battery > LP_BATTERY_THRESHOLD ) ))" + fi + } + ;; + Darwin) + _lp_battery() { + (( LP_ENABLE_BATT )) || return 5 + local batt_status + IFS=';' read -r lp_battery batt_status <<<"$(pmset -g batt | sed -n 's/^ -InternalBattery.*[[:space:]]\([0-9]*[0-9]\)%; \([^;]*\).*$/\1;\2/p')" + case "$batt_status" in + charged | "") + return 4 + ;; + discharging) + # under => 0, above => 1 + return "$(( lp_battery > LP_BATTERY_THRESHOLD ))" + ;; + *) # "charging", "AC attached" + # under => 2, above => 3 + return "$(( 2 + ( lp_battery > LP_BATTERY_THRESHOLD ) ))" + ;; + esac + } + ;; + *) + _lp_battery() { + return 5 + } + ;; +esac + +# Compute a gradient of background/foreground colors depending on the battery status. +_lp_battery_color() { + (( LP_ENABLE_BATT )) || return 2 + + _lp_battery + local -i _status="$?" + + if (( _status >= 4 || lp_battery == 100 )); then + # no battery support or battery full: nothing displayed + return 1 + elif (( _status == 3 && lp_battery != 100 )); then + # charging and above threshold and not 100% + # green ⏚ + lp_battery_color="${LP_COLOR_CHARGING_ABOVE}${LP_MARK_ADAPTER}${NO_COL}" + elif (( _status == 2 )); then + # charging but under threshold + # yellow ⏚ + lp_battery_color="${LP_COLOR_CHARGING_UNDER}${LP_MARK_ADAPTER}${NO_COL}" + elif (( _status == 1 )); then + # discharging but above threshold + # yellow ⌁ + lp_battery_color="${LP_COLOR_DISCHARGING_ABOVE}${LP_MARK_BATTERY}${NO_COL}" + # discharging and under threshold + else + lp_battery_color="${LP_COLOR_DISCHARGING_UNDER}${LP_MARK_BATTERY}${NO_COL}" + + if (( LP_PERCENTS_ALWAYS )); then + local -i idx + if (( lp_battery <= 0 )); then + idx=0 + elif (( lp_battery <= 5 )); then # 5 + idx=9 + elif (( lp_battery <= 10 )); then # 5 + idx=8 + elif (( lp_battery <= 20 )); then # 10 + idx=7 + elif (( lp_battery <= 30 )); then # 10 + idx=6 + elif (( lp_battery <= 40 )); then # 10 + idx=5 + elif (( lp_battery <= 50 )); then # 10 + idx=4 + elif (( lp_battery <= 65 )); then # 15 + idx=3 + elif (( lp_battery <= 80 )); then # 15 + idx=2 + elif (( lp_battery < 100 )); then # 20 + idx=1 + else # >= 100 + idx=0 + fi + + local ret + _lp_color_map "$idx" 10 + lp_battery_color+="${ret}${lp_battery}${_LP_PERCENT}${NO_COL}" + fi + fi +} + +########################### +# Runtime of last command # +########################### + +_lp_runtime_format() { + (( LP_ENABLE_RUNTIME )) || return 2 + + lp_runtime_format= + if (( _LP_RUNTIME_SECONDS >= LP_RUNTIME_THRESHOLD )); then + # display runtime seconds as days, hours, minutes, and seconds + (( _LP_RUNTIME_SECONDS >= 86400 )) && lp_runtime_format=$((_LP_RUNTIME_SECONDS / 86400))d + (( _LP_RUNTIME_SECONDS >= 3600 )) && lp_runtime_format+=$((_LP_RUNTIME_SECONDS % 86400 / 3600))h + (( _LP_RUNTIME_SECONDS >= 60 )) && lp_runtime_format+=$((_LP_RUNTIME_SECONDS % 3600 / 60))m + lp_runtime_format+=$((_LP_RUNTIME_SECONDS % 60))s + else + return 1 + fi +} + +__lp_runtime_before() { + _LP_RUNTIME_LAST_SECONDS=$SECONDS + _LP_RUNTIME_SECONDS=-1 +} + +# Compute number of seconds since the command was started +__lp_runtime_after() { + if [[ -n "${_LP_RUNTIME_LAST_SECONDS-}" ]]; then + (( _LP_RUNTIME_SECONDS=SECONDS-_LP_RUNTIME_LAST_SECONDS )) + unset _LP_RUNTIME_LAST_SECONDS + fi +} + +_lp_runtime_color() { + _lp_runtime_format || return "$?" + + lp_runtime_color="${LP_COLOR_RUNTIME}${lp_runtime_format}${NO_COL}" +} + +############### +# System load # +############### + +# Get CPU count and current load +case "$LP_OS" in + Linux) + __lp_cpu_count() { + _lp_CPUNUM=$( nproc 2>/dev/null || GREP_OPTIONS='' \grep -c '^[Pp]rocessor' /proc/cpuinfo ) + } + _lp_cpu_load () { + local _ IFS=$' \t' + read -r lp_cpu_load _ < /proc/loadavg + } + ;; + FreeBSD|Darwin|OpenBSD) + __lp_cpu_count() { + _lp_CPUNUM=$( sysctl -n hw.ncpu ) + } + _lp_cpu_load () { + local _ IFS=$' \t' + # If you have problems with syntax coloring due to the following + # line, do this: ln -s liquidprompt liquidprompt.bash + # and edit liquidprompt.bash + read -r _ lp_cpu_load _ <<<"$( LC_ALL=C sysctl -n vm.loadavg )" + } + ;; + SunOS) + __lp_cpu_count() { + _lp_CPUNUM=$( kstat -m cpu_info | GREP_OPTIONS='' \grep -c "module: cpu_info" ) + } + _lp_cpu_load () { + lp_cpu_load="$( LC_ALL=C uptime | sed 's/.*load average: *\([0-9.]*\).*/\1/' )" + } +esac + +_lp_load() { + (( LP_ENABLE_LOAD )) || return 2 + + local lp_cpu_load ret + + # Get value (OS-specific) into lp_cpu_load + _lp_cpu_load + + __lp_escape "${lp_cpu_load:-0}" + lp_load=$ret + + __lp_floating_scale "${lp_cpu_load:-0}" 100 + lp_load_adjusted=$(( ret / _lp_CPUNUM )) + + (( lp_load_adjusted >= _LP_LOAD_THRESHOLD )) +} + +# Compute a gradient of background/forground colors depending on the load. +_lp_load_color() { + _lp_load || return "$?" + + local ret + _lp_color_map "$lp_load_adjusted" "$_LP_LOAD_CAP" + lp_load_color="${ret}${LP_MARK_LOAD}" + + if (( LP_PERCENTS_ALWAYS )); then + lp_load_color+="${lp_load}" + fi + lp_load_color+="$NO_COL" +} + +###################### +# System temperature # +###################### + +# Backends for TEMP. Each backend must return the result in $lp_temperature. +# Return the hottest system temperature we get. + +__lp_temp_sysfs() { + local -i temperature + + for temp_file in ${_LP_LINUX_TEMPERATURE_FILES[@]+"${_LP_LINUX_TEMPERATURE_FILES[@]}"}; do + if [[ ! -r $temp_file ]]; then + continue + fi + + IFS='' read -r temperature <"$temp_file" 2>/dev/null || continue + # Input is in millidegrees Celsius. + (( temperature = temperature / 1000 )) + + if [[ -z ${lp_temperature-} ]] || (( temperature > ${lp_temperature:-0} )); then + lp_temperature=$temperature + fi + done +} + +# Implementation using lm-sensors +__lp_temp_sensors() { + # Return the hottest system temperature we get through the sensors command + # Only the integer part is retained + local -i i + local IFS=$' \t\n' + for i in $(LC_ALL=C \sensors -u 2>/dev/null | + sed -n 's/^ temp[0-9][0-9]*_input: \([0-9]*\)\..*$/\1/p'); do + if [[ -z ${lp_temperature-} ]] || (( i > ${lp_temperature:-0} )); then + lp_temperature=$i + fi + done +} + +# Implementation using 'acpi -t' +__lp_temp_acpi() { + local -i i + local IFS=$' \t\n' + # Only the integer part is retained + for i in $(LC_ALL=C \acpi -t | + sed -n 's/.* \(-\{0,1\}[0-9]*\)\.[0-9]* degrees C$/\1/p'); do + if [[ -z ${lp_temperature-} ]] || (( i > ${lp_temperature:-0} )); then + lp_temperature=$i + fi + done +} + +# Dynamic selection of backend +__lp_temp_detect() { + local lp_temperature cmd + + if [[ $LP_OS == 'Linux' ]]; then + _LP_TEMP_FUNCTION="__lp_temp_sysfs" + # Check that we can retrieve temperature at least once + "$_LP_TEMP_FUNCTION" 2>/dev/null + # If $lp_temperature is set, success! + [[ -n "${lp_temperature-}" ]] && return 0 + fi + + for cmd in acpi sensors ; do + command -v "$cmd" >/dev/null || continue + + _LP_TEMP_FUNCTION="__lp_temp_$cmd" + "$_LP_TEMP_FUNCTION" 2>/dev/null + + [[ -n "${lp_temperature-}" ]] && return 0 + done + + unset _LP_TEMP_FUNCTION + return 1 +} + +# Returns current highest system temperature. +_lp_temperature() { + (( LP_ENABLE_TEMP )) || return 2 + + unset lp_temperature + "$_LP_TEMP_FUNCTION" + + [[ -z ${lp_temperature-} ]] && return 1 + (( lp_temperature >= LP_TEMP_THRESHOLD )) +} + +# Display the numeric value as we get from _lp_temperature and colorize it through _lp_color_map. +_lp_temperature_color() { + _lp_temperature || return "$?" + + local ret + _lp_color_map "$lp_temperature" 120 + lp_temperature_color="${LP_MARK_TEMP}${ret}${lp_temperature}°${NO_COL}" +} + +########## +# Title # +########## + +# Deprecated since 2.0 +_lp_title() { + (( LP_ENABLE_TITLE )) || return + + # Get the input as pure text + local ret + __lp_strip_escapes "${1-}" + printf '%s' "${_LP_OPEN_ESC}${LP_TITLE_OPEN}${ret}${LP_TITLE_CLOSE}${_LP_CLOSE_ESC}" +} + +_lp_formatted_title() { + (( LP_ENABLE_TITLE )) || return 2 + + # Get the input as pure text + local ret + __lp_strip_escapes "${1-}" + _lp_generated_title="$ret" +} + +_lp_raw_title() { + (( LP_ENABLE_TITLE )) || return 2 + + _lp_generated_title=${1-} +} + +lp_title() { + (( LP_ENABLE_TITLE )) || return 2 + + if [[ -n ${1+x} ]]; then + _lp_manual_title=$1 + else + unset _lp_manual_title + fi +} + +if (( _LP_SHELL_zsh )); then + __lp_get_last_command_line() { + # shellcheck disable=SC2154 + command=${history[$HISTCMD]} + } +else + __lp_get_last_command_line() { + command=$(HISTTIMEFORMAT='' builtin history 1 2>/dev/null) + command=${command#*[[:digit:]][* ] } + + # Fallback measure if something goes wrong. + if [[ -z $command ]]; then + command=$BASH_COMMAND + fi + } +fi + +__lp_print_title_command() { + local command + __lp_get_last_command_line + + printf '%s' "${LP_TITLE_OPEN}${_lp_manual_title:-${_lp_generated_title-${SHELL+"$SHELL \$ "}}}${command}${LP_TITLE_CLOSE}" +} + +################### +# CURRENT TIME # +################### + +# The targeted unicode characters are the "CLOCK FACE" ones +# They are located in the codepages between: +# U+1F550 (ONE OCLOCK) and U+1F55B (TWELVE OCLOCK), for the plain hours +# U+1F55C (ONE-THIRTY) and U+1F567 (TWELVE-THIRTY), for the thirties +# Generated with: +# perl -C -E 'say join("", map {chr(0x1F550+$_)." ".chr(0x1F55C+$_)." "} 0..11)' +_LP_CLOCK=(🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧 ) + +_lp_analog_time() { + (( LP_ENABLE_TIME && LP_TIME_ANALOG )) || return 2 + + # %I: "00".."12" %M: "00".."59" + # hh: 1..12 mm: 0..59 + local hh mm + IFS=' ' read -r hh mm <<<"$(date '+%I %M')" + + # Bash interprets a '0' prefix as octal + # so we have to clean that + hh="${hh#0}" + mm="${mm#0}" + + # clock: 0 .. 25 + # 1:00..1:14 -> 0 + # 1:15..1:44 -> 1 + # 1:45..2:15 -> 2 + # ... + # 12:15..12:44 -> 23 + # 12:45..12:59 -> 0 + # There is a space just after the clock char because the glyph + # width is twice usual glyphs + lp_analog_time="${_LP_CLOCK[((hh*60+mm-45)/30)%24+_LP_FIRST_INDEX]} " +} + +_lp_analog_time_color() { + _lp_analog_time || return "$?" + + lp_analog_time_color="${LP_COLOR_TIME}${lp_analog_time}${NO_COL}" +} + +_lp_time() { + (( LP_ENABLE_TIME && ! LP_TIME_ANALOG )) || return 2 + + local ret + __lp_escape "$(date "+${LP_TIME_FORMAT}")" + lp_time=$ret +} + +_lp_time_color() { + _lp_time || return "$?" + + lp_time_color="${LP_COLOR_TIME}${lp_time}${NO_COL}" +} + +####################### +# Container detection # +####################### +_lp_container() { + (( LP_ENABLE_CONTAINER )) || return 2 + + lp_container="" + + if [[ -n "${SINGULARITY_CONTAINER-}" || -n "${SINGULARITY_NAME-}" ]]; then + lp_container="Singlrty" + elif [[ -e /run/.containerenv ]]; then + if [[ -f /run/.toolboxenv ]]; then + lp_container="Toolbox" + else + lp_container="Podman" + fi + elif [[ -e /proc/self/cgroup ]]; then + local cgroup="$(< /proc/self/cgroup)" + + if [[ "$cgroup" == *"docker"* ]]; then + lp_container="Docker" + elif [[ "$cgroup" == *"lxc"* ]]; then + lp_container="LXC" + else + return 1 + fi + elif [[ -r /run/host/container-manager ]]; then + local ret + IFS='' read -r ret < /run/host/container-manager + __lp_escape "${ret#systemd-}" + lp_container="$ret" + else + return 1 + fi +} + +_lp_container_color() { + unset lp_container_color + _lp_container || return "$?" + + lp_container_color="«${LP_COLOR_CONTAINER}${lp_container}${NO_COL}»" +} + + +_lp_dev_env_color() { + # add development environments + local -a dev_all + dev_all=( + "${lp_modules_color-}" + "${lp_software_collections_color-}" + "${lp_aws_profile_color-}" + "${lp_container_color-}" + "${lp_python_env_color-}" + "${lp_node_env_color-}" + "${lp_ruby_env_color-}" + "${lp_terraform_env_color-}" + "${lp_kubernetes_context_color-}" + "${lp_cmake_color-}" + "${lp_os_color-}" + ) + local -a dev_active + dev_active=() + for i in "${dev_all[@]}"; do + if [[ "$i" ]]; then + dev_active+=("$i") + fi + done + if [[ ${#dev_active[@]} -gt 0 ]] ; then + local lp_join + _lp_join "${LP_MARK_DEV_MID}" "${dev_active[@]}" + lp_dev_env_color="${LP_MARK_DEV_OPEN}${lp_join}${LP_MARK_DEV_CLOSE}" + return 0 + else + unset lp_dev_env_color + return 1 + fi +} + + +################# +# Default theme # +################# + +_lp_default_theme_activate() { + # Default value for LP_PERM when LP_ENABLE_PERM is 0 + LP_PERM=${LP_MARK_PERM} # without color + + _lp_user + local -i user_type="$?" + + if (( user_type < 2 )); then # if user is not root + if (( LP_ENABLE_SUDO )); then + LP_COLOR_MARK_NO_SUDO="$LP_COLOR_MARK" + fi + else # root! + if (( ! LP_ENABLE_VCS_ROOT )); then + LP_MARK_DISABLED="$LP_MARK_DEFAULT" + fi + fi + + # The user or connection type is not expected to change from inside the + # shell, so we build this just once. + if _lp_username_color; then + LP_USER="$lp_username_color" + else + LP_USER= + fi + + _lp_hostname_color + LP_HOST="$lp_hostname_color" + + # If we are running in a terminal multiplexer, brackets are colored + if _lp_multiplexer; then + LP_BRACKET_OPEN="${LP_COLOR_IN_MULTIPLEXER}${LP_MARK_MULTIPLEXER_OPEN}${NO_COL}" + LP_BRACKET_CLOSE="${LP_COLOR_IN_MULTIPLEXER}${LP_MARK_MULTIPLEXER_CLOSE}${NO_COL}" + else + LP_BRACKET_OPEN="${LP_MARK_BRACKET_OPEN}" + LP_BRACKET_CLOSE="${LP_MARK_BRACKET_CLOSE}" + fi + + _lp_terminal_device + # shellcheck disable=SC2034 + LP_TTYN=$lp_terminal_device +} + +_lp_default_theme_directory() { + # LP_PERM: shows a ":" + # - colored in green if user has write permission on the current dir + # - colored in red if not + # - can set another symbol with LP_MARK_PERM + if (( LP_ENABLE_PERM )); then + if [[ -w "${PWD}" ]]; then + LP_PERM="${LP_COLOR_WRITE}${LP_MARK_PERM}${NO_COL}" + else + LP_PERM="${LP_COLOR_NOWRITE}${LP_MARK_PERM}${NO_COL}" + fi + fi + + local lp_path_format + _lp_path_format "$LP_COLOR_PATH" "$LP_COLOR_PATH_LAST_DIR" "$LP_COLOR_PATH_VCS_ROOT" "$LP_COLOR_PATH_SHORTENED" "/" "$LP_COLOR_PATH_SEPARATOR" + + LP_PWD="${lp_path_format}${NO_COL}" +} + +# Do not complain about unused variables. +# shellcheck disable=SC2034 +_lp_default_theme_prompt_data() { + # left of main prompt: space at right + if _lp_jobcount_color; then + LP_JOBS="$lp_jobcount_color " + else + LP_JOBS= + fi + if _lp_temperature_color; then + LP_TEMP="$lp_temperature_color " + else + LP_TEMP= + fi + if _lp_load_color; then + LP_LOAD="$lp_load_color " + else + LP_LOAD= + fi + if _lp_battery_color; then + LP_BATT="$lp_battery_color " + else + LP_BATT= + fi + if _lp_wifi_signal_strength_color; then + LP_WIFI="$lp_wifi_signal_strength_color " + else + LP_WIFI= + fi + + if _lp_time_color; then + LP_TIME="$lp_time_color " + elif _lp_analog_time_color; then + LP_TIME="$lp_analog_time_color " + else + LP_TIME= + fi + + if _lp_sudo_active_color; then + LP_COLOR_MARK="$lp_sudo_active_color" + fi + + if _lp_dirstack_color; then + LP_DIRSTACK=" $lp_dirstack_color" + else + LP_DIRSTACK= + fi + + # in main prompt: no space + if _lp_http_proxy_color; then + LP_PROXY="$lp_http_proxy_color" + else + LP_PROXY= + fi + + if _lp_env_vars_color; then + LP_ENVVARS="$lp_env_vars_color" + else + LP_ENVVARS= + fi + + if _lp_shell_level_color; then + LP_SHLVL="$lp_shell_level_color" + else + LP_SHLVL= + fi + + # All the following "dev-related" variables may be used alone by theme designers. + # If you need the whole section, you can also use LP_DEV_ENV defined below. + + if _lp_python_env_color; then + LP_VENV=" $lp_python_env_color" + else + LP_VENV= + fi + + if _lp_node_env_color; then + LP_NODE_VENV=" $lp_node_env_color" + else + LP_NODE_VENV= + fi + + if _lp_ruby_env_color; then + LP_RUBY_VENV=" $lp_ruby_env_color" + else + LP_RUBY_VENV= + fi + + if _lp_kubernetes_context_color; then + LP_KUBECONTEXT=" $lp_kubernetes_context_color" + else + LP_KUBECONTEXT= + fi + + if _lp_terraform_env_color; then + LP_TFSPACE=" $lp_terraform_env_color" + else + LP_TFSPACE= + fi + + if _lp_container_color; then + LP_CONTAINER=" $lp_container_color" + else + LP_CONTAINER= + fi + + if _lp_software_collections_color; then + LP_SCLS=" $lp_software_collections_color" + else + LP_SCLS= + fi + + if _lp_aws_profile_color; then + LP_AWS_PROFILE=" $lp_aws_profile_color" + else + LP_AWS_PROFILE= + fi + + if _lp_cmake_color; then + LP_CMAKE=" $lp_cmake_color" + else + LP_CMAKE= + fi + + if _lp_os_color; then + LP_OPSYS=" $lp_os_color" + else + LP_OPSYS= + fi + + if _lp_modules_color; then + LP_MODULES=" $lp_modules_color" + else + LP_MODULES= + fi + + # End of the section for dev-related variables. + + # Alternative to atomic variables above: + # a correctly separated string list with everything. + if _lp_dev_env_color ; then + LP_DEV_ENV=" $lp_dev_env_color" + else + LP_DEV_ENV= + fi + + if _lp_runtime_color; then + LP_RUNTIME=" $lp_runtime_color" + else + LP_RUNTIME= + fi + + if _lp_error_color; then + LP_ERR=" $lp_error_color" + else + LP_ERR= + fi + + if _lp_error_meaning_color; then + LP_ERR_MEANING="$lp_error_meaning_color" + else + LP_ERR_MEANING= + fi + + if _lp_find_vcs && _lp_vcs_details_color; then + LP_VCS=" $lp_vcs_details_color" + else + LP_VCS= + fi + + _lp_smart_mark + LP_MARK="${lp_smart_mark}${NO_COL} " +} + +_lp_default_theme_prompt_template() { + if [[ -f "${LP_PS1_FILE-}" ]]; then + # shellcheck source=liquid.ps1 + source "$LP_PS1_FILE" + fi + + if [[ -z "${LP_PS1-}" ]]; then + # add title escape time, jobs, load and battery + PS1="${LP_PS1_PREFIX}${LP_TIME}${LP_BATT}${LP_LOAD}${LP_TEMP}${LP_WIFI}${LP_JOBS}" + # add user, host, permissions colon, working directory, and dirstack + PS1+="${LP_BRACKET_OPEN}${LP_USER}${LP_HOST}${LP_PERM}${LP_PWD}${LP_DIRSTACK}${LP_BRACKET_CLOSE}${LP_PROXY}${LP_ENVVARS}${LP_SHLVL}" + + # Add the list of development environments/config/etc. + PS1+="${LP_DEV_ENV}" + + # Add VCS infos + # If root, the info has not been collected unless LP_ENABLE_VCS_ROOT + # is set. + PS1+="${LP_VCS}" + + # add return code and prompt mark + PS1+="${LP_RUNTIME}${LP_ERR}${LP_ERR_MEANING}${LP_MARK_PREFIX}${LP_COLOR_MARK}${LP_MARK}${LP_PS1_POSTFIX}" + + # Get the core sections without prompt escapes and make them into a title. + _lp_formatted_title "${LP_PS1_PREFIX}${LP_BRACKET_OPEN}${LP_USER}${LP_HOST}${LP_MARK_PERM}${lp_path-}${LP_BRACKET_CLOSE}${LP_MARK_PREFIX}${LP_MARK}${LP_PS1_POSTFIX}" + else + PS1=$LP_PS1 + fi +} + +_lp_default_theme_prompt() { + _lp_default_theme_prompt_data + _lp_default_theme_prompt_template +} + +######################## +# Construct the prompt # +######################## + +__lp_set_prompt() { + # Display the return value of the last command, if different from zero + # As this get the last returned code, it should be called first + local -i lp_error="$?" + + if (( LP_ENABLE_RUNTIME || LP_ENABLE_RUNTIME_BELL )); then + __lp_runtime_after + fi + + # bash: execute the old prompt hook + if [[ -n ${LP_OLD_PROMPT_COMMAND-} ]]; then + eval "$LP_OLD_PROMPT_COMMAND" + fi + + if (( LP_ENABLE_RUNTIME_BELL && ${_LP_RUNTIME_SECONDS-0} >= LP_RUNTIME_BELL_THRESHOLD )); then + printf '%s' "$_LP_TI_BELL" + fi + + # Localize cache data variables + local _lp_git_diff_shortstat_uncommitted _lp_git_diff_shortstat_unstaged _lp_git_diff_shortstat_staged + + # if change of working directory + if [[ "${LP_OLD_PWD-}" != "LP:$PWD" ]]; then + # Update directory icon for MacOS X + "$_LP_TERM_UPDATE_DIR" + + "$_LP_THEME_DIRECTORY_FUNCTION" + + # Prefix with 'LP:' to prevent Zsh with AUTO_NAME_DIRS enabled using + # this var as a name for the working directory, that will be used by + # the '%' and related prompt sequences. + # See https://github.com/nojhan/liquidprompt/issues/124 for details. + LP_OLD_PWD="LP:$PWD" + fi + + "$_LP_THEME_PROMPT_FUNCTION" + + if (( LP_ENABLE_TITLE )); then + printf '%s' "${LP_TITLE_OPEN}${_lp_manual_title:-${_lp_generated_title-${SHELL-}}}${LP_TITLE_CLOSE}" + fi +} + +__lp_before_command() { + # For debugging + #printf 'XXX %s\n' "$BASH_COMMAND" + + # If this is the first time after the user submitted the command, + # execute the hooks. + if (( _LP_AT_PROMPT )); then + _LP_AT_PROMPT=0 + + if (( LP_ENABLE_RUNTIME || LP_ENABLE_RUNTIME_BELL )); then + __lp_runtime_before + fi + + if (( LP_ENABLE_TITLE_COMMAND )); then + __lp_print_title_command + fi + fi + + # If this is when the prompt is being drawn, the command is done, + # so mark the next trap. Note these two events could be at the same + # time, so no elif is used. + if [[ "$BASH_COMMAND" == __lp_set_prompt ]]; then + _LP_AT_PROMPT=1 + fi + +} + +prompt_tag() { + if [[ -n "${1-}" ]]; then + export LP_PS1_PREFIX="$1 " + else + export LP_PS1_PREFIX= + fi +} + +# Activate Liquid Prompt +prompt_on() { + # Reset so all PWD dependent variables are computed after loading + LP_OLD_PWD="" + + # if Liquid Prompt has not been already set + if [[ -z "${LP_OLD_PS1-}" ]]; then + LP_OLD_PS1="$PS1" + fi + + if (( _LP_SHELL_bash )); then + # Prevent some cases where the user shoots in his own foot. + # PROMPT_COMMAND is not exported by default, but some users + # incorrectly export it from their profile/bashrc (GitHub #450), + # so we preventively UNexport it. + # TODO: warn the user if it was exported + if (( ${BASH_VERSINFO[0]:-0} > 4 || ( ${BASH_VERSINFO[0]:-0} == 4 && ${BASH_VERSINFO[1]:-0} >= 2 ) )); then + # -g is only available since bash 4.2 + declare -g +x PROMPT_COMMAND + fi + + if ! __lp_use_bash_preexec; then + local set_prompt_command + if (( LP_DEBUG_TIME )); then + set_prompt_command="time __lp_set_prompt" + else + set_prompt_command=__lp_set_prompt + fi + + if (( ${BASH_VERSINFO[0]:-0} > 5 || ( ${BASH_VERSINFO[0]:-0} == 5 && ${BASH_VERSINFO[1]:-0} >= 1 ) )); then + # PROMPT_COMMAND is an array since bash 5.1 + PROMPT_COMMAND+=( "$set_prompt_command" ) + + else + if [[ -z ${LP_OLD_PROMPT_COMMAND+x} ]]; then + LP_OLD_PROMPT_COMMAND="${PROMPT_COMMAND-}" + fi + + # shellcheck disable=SC2178 + PROMPT_COMMAND="$set_prompt_command" + fi + + if (( LP_ENABLE_RUNTIME || LP_ENABLE_RUNTIME_BELL || LP_ENABLE_TITLE_COMMAND )); then + _LP_AT_PROMPT=0 + _LP_RUNTIME_LAST_SECONDS=$SECONDS + # __lp_before_command gets called just before bash executes a command, + # including $PROMPT_COMMAND + # Pass $_ to this call, because it sets $_ to what it already was + trap '__lp_before_command "$_"' DEBUG + fi + else + # We do not want __lp_set_prompt to show up twice in precmd_functions; if it does, then LP_ENABLE_ERROR + # breaks because even if the first call of __lp_set_prompt has $? != 0, the second one will have $? == 0. + # (Same for __lp_debug_timed_lp_set_prompt.) + + # This conditional is intended to check $precmd_functions for the presence of '__lp_set_prompt' or + # '__lp_debug_timed_lp_set_prompt' so they get added at most once + if ! ( __lp_array_contains __lp_set_prompt ${precmd_functions[@]+"${precmd_functions[@]}"} \ + || __lp_array_contains __lp_debug_timed_set_prompt ${precmd_functions[@]+"${precmd_functions[@]}"} ); + then + if (( LP_DEBUG_TIME )); then + __lp_debug_timed_lp_set_prompt() { + time __lp_set_prompt + } + precmd_functions+=(__lp_debug_timed_lp_set_prompt) + else + precmd_functions+=(__lp_set_prompt) + fi + fi + if (( LP_ENABLE_RUNTIME || LP_ENABLE_RUNTIME_BELL )); then + _LP_RUNTIME_LAST_SECONDS=$SECONDS + # It's less bad to have this be duped than __lp_set_prompt, but let's be sure + if ! ( __lp_array_contains __lp_runtime_before ${preexec_functions[@]+"${preexec_functions[@]}"} ); then + preexec_functions+=(__lp_runtime_before) + fi + fi + if (( LP_ENABLE_TITLE_COMMAND )); then + # It's less bad to have this be duped than __lp_set_prompt, but let's be sure + if ! ( __lp_array_contains __lp_print_title_command ${preexec_functions[@]+"${preexec_functions[@]}"} ); then + preexec_functions+=(__lp_print_title_command) + fi + fi + fi + else # zsh + if [[ -n "${prompt_theme-}" && "$prompt_theme" != off ]]; then + _LP_ZSH_PROMPT_THEME="$prompt_theme" + # Disable the prompt to disable its precmd hook + prompt off + fi + + if [[ -z ${_LP_OLD_SETOPT-} ]]; then + # Dump option names: echo ${(ko)options} + # shellcheck disable=SC2154 + if [[ "${options[promptpercent]}" == on ]]; then + _LP_OLD_SETOPT="promptpercent" + else + _LP_OLD_SETOPT="nopromptpercent" + fi + fi + + # Set options that affect PS1 evaluation; enable percent expansion + setopt promptpercent + + add-zsh-hook precmd __lp_set_prompt + + if (( LP_ENABLE_RUNTIME || LP_ENABLE_RUNTIME_BELL )); then + _LP_RUNTIME_LAST_SECONDS=$SECONDS + add-zsh-hook preexec __lp_runtime_before + fi + if (( LP_ENABLE_TITLE_COMMAND )); then + add-zsh-hook preexec __lp_print_title_command + fi + fi +} + +__lp_disable_hooks() { + if (( _LP_SHELL_bash )); then + if __lp_use_bash_preexec; then + # Disable previous hooks as options that set them may have changed. + for i in ${precmd_functions[@]+"${!precmd_functions[@]}"}; do + local value="${precmd_functions[i]}" + if [[ $value == "__lp_set_prompt" || $value == "__lp_debug_timed_lp_set_prompt" ]]; then + unset 'precmd_functions[i]' + fi + done + + for i in ${preexec_functions[@]+"${!preexec_functions[@]}"}; do + local value="${preexec_functions[i]}" + if [[ $value == "__lp_runtime_before" || $value == "__lp_print_title_command" ]]; then + unset 'preexec_functions[i]' + fi + done + else + if (( ${BASH_VERSINFO[0]:-0} > 5 || ( ${BASH_VERSINFO[0]:-0} == 5 && ${BASH_VERSINFO[1]:-0} >= 1 ) )); then + # PROMPT_COMMAND is an array since bash 5.1 + for i in ${PROMPT_COMMAND[@]+"${!PROMPT_COMMAND[@]}"}; do + local value="${PROMPT_COMMAND[i]}" + if [[ $value == "__lp_set_prompt" || $value == "time __lp_set_prompt" ]]; then + unset 'PROMPT_COMMAND[i]' + fi + done + else + if [[ -n ${LP_OLD_PROMPT_COMMAND+x} ]]; then + # shellcheck disable=SC2178 + PROMPT_COMMAND="${LP_OLD_PROMPT_COMMAND-}" + unset LP_OLD_PROMPT_COMMAND + fi + fi + + # Disable the DEBUG trap used by the RUNTIME or TITLE_COMMAND features + if (( ${LP_ENABLE_RUNTIME-0} || ${LP_ENABLE_RUNTIME_BELL-0} || ${LP_ENABLE_TITLE_COMMAND-0} )); then + trap - DEBUG + fi + fi + else # zsh + # Disable previous hooks as options that set them + # may have changed + { + add-zsh-hook -d precmd __lp_set_prompt + add-zsh-hook -d preexec __lp_runtime_before + add-zsh-hook -d preexec __lp_print_title_command + } >/dev/null + fi +} + +# Come back to the old prompt +prompt_off() { + __lp_disable_hooks + + PS1=$LP_OLD_PS1 + + if (( _LP_SHELL_zsh )); then + setopt "${_LP_OLD_SETOPT}" + + if [[ -n ${_LP_ZSH_PROMPT_THEME-} ]]; then + prompt "$_LP_ZSH_PROMPT_THEME" + unset _LP_ZSH_PROMPT_THEME + fi + fi +} + +# Use an empty prompt: just the \$ mark +prompt_OFF() { + __lp_disable_hooks + + PS1="$_LP_MARK_SYMBOL " +} + +lp_theme() { + local theme="${1-}" + + if [[ $theme == '--list' ]]; then + local -a lp_theme_list + __lp_theme_list + printf '%s\n' "${lp_theme_list[@]}" + return + fi + + local f_prompt="_lp_${theme}_theme_prompt" f_dir="_lp_${theme}_theme_directory" f_activate="_lp_${theme}_theme_activate" + + if [[ -z $theme ]]; then + printf '%s\n%s\n' \ + 'Must pass in the name of a theme. If you meant the default Liquid Prompt theme, try "default".' \ + 'Run "lp_theme --list" to see all loaded and available themes.' 2>&1 + return 1 + fi + + if ! __lp_is_function "$f_prompt"; then + printf 'Loading theme "%s" failed: cannot find function "%s". Please source the theme file first.\n' \ + "$theme" "$f_prompt" 2>&1 + return 2 + fi + if ! __lp_is_function "$f_dir"; then + f_dir=":" + fi + if ! __lp_is_function "$f_activate"; then + f_activate=":" + fi + + _LP_THEME_ACTIVATE_FUNCTION=$f_activate + _LP_THEME_DIRECTORY_FUNCTION=$f_dir + _LP_THEME_PROMPT_FUNCTION=$f_prompt + + "$f_activate" + prompt_on +} + +# By default, sourcing 'liquidprompt' will activate Liquid Prompt +if [ "${1-}" != "--no-activate" ]; then + lp_activate +fi + +# vim: set et sts=4 sw=4 tw=120 ft=sh: diff --git a/.bashrc.d/99-autostart.bashrc b/.bashrc.d/99-autostart.bashrc new file mode 100644 index 0000000..3cb6f68 --- /dev/null +++ b/.bashrc.d/99-autostart.bashrc @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# ~/.bashrc: executed by bash(1) for non-login shells. + +# ============================== +# AUTOSTART +# ============================== + +# run tmux if TMUX variable is null +if [[ "${TERM-}" != tmux* ]] && [[ -z "${TMUX}" ]] && [[ -z "${SSH_CONNECTION}" ]]; then + tmuxa +elif [[ -z "${TMUX}" ]] && [[ -n "${SSH_CONNECTION}" ]]; then + tmuxs +fi +# run MOTD at login only in an ssh session +if [[ -n "${TMUX}" ]] && [[ -n "${SSH_CONNECTION}" ]] && [[ "${SSH_MOTD-}" == 1 ]] && [[ -f "/etc/update-motd.d/00-motdfetch" ]]; then + motd +else + # use function pgdown to get the prompt to terminal window bottom + pgdown +fi diff --git a/.profile b/.profile new file mode 100644 index 0000000..fd03a03 --- /dev/null +++ b/.profile @@ -0,0 +1,60 @@ +# ~/.profile: executed by the command interpreter for login shells. +# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login +# exists. + +# if running with bash +if [ -n "$BASH_VERSION" ]; then + # include .bashrc if it exists + if [ -f "$HOME/.bashrc" ]; then + . "$HOME/.bashrc" + fi +else + # Expanding PATH with users bin + if [ -d "$HOME/.local/bin" ] ; then + export PATH="$HOME/.local/bin:$PATH" + fi + + # Check if XDG environment variables are set or set them to default + if [ -z "$XDG_DATA_HOME" ]; then + export XDG_DATA_HOME=$HOME/.local/share + fi + if [ -z "$XDG_CONFIG_HOME" ]; then + export XDG_CONFIG_HOME=$HOME/.config + fi + if [ -z "$XDG_STATE_HOME" ]; then + export XDG_STATE_HOME=$HOME/.local/state + fi + if [ -z "$XDG_CACHE_HOME" ]; then + export XDG_CACHE_HOME=$HOME/.cache + fi + + # ============================== + # Languages + # ============================== + + # Cargo env config + if [ -d "$HOME/.cargo/bin" ]; then + export PATH="$HOME/.cargo/bin:$PATH" + fi + + # Nim env config + if [ -d "$HOME/.nimble/bin" ]; then + export PATH="$HOME/.nimble/bin:$PATH" + fi + + # Go env config + if [ -d "$HOME/go/bin" ]; then + export PATH="$HOME/go/bin:$PATH" + fi + + # Deno env config + if [ -f "$HOME/.deno/bin/deno" ]; then + export DENO_INSTALL="$HOME/.deno" + export PATH="$DENO_INSTALL/bin:$PATH" + fi + + # opt-out for .NET telemetry + if command -v dontnet 1> /dev/null; then + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + fi +fi