################################################################################
# LIQUID PROMPT
# An intelligent and non-intrusive prompt for Bash and zsh
################################################################################

# 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 <http://www.gnu.org/licenses/>.

# 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
[ "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_USER_SYMBOL="\u"
    _LP_HOST_SYMBOL="\h"
    _LP_FQDN_SYMBOL="\H"
    _LP_TIME_SYMBOL="\t"
    _LP_MARK_SYMBOL='\$'

    _LP_FIRST_INDEX=0
    _LP_PERCENT='%'    # must be escaped on zsh
    _LP_BACKSLASH='\\' # must be escaped on bash

    # Sed expression using extended regexp to match terminal
    # escape sequences with their wrappers
    _LP_CLEAN_ESC='\\\[([^\]+|\\[^]])*\\\]'

    # 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//\\/\\\\}"
    }
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_USER_SYMBOL="%n"
    _LP_HOST_SYMBOL="%m"
    _LP_FQDN_SYMBOL="%M"
    _LP_TIME_SYMBOL="%*"
    _LP_MARK_SYMBOL='%(!.#.%%)'

    _LP_FIRST_INDEX=1
    _LP_PERCENT='%%'
    _LP_BACKSLASH="\\"

    _LP_CLEAN_ESC='%\{([^%]+|%[^}])*%\}'

    __lp_escape() {
        local arg="${1//\\/\\\\}"
        ret="${arg//\%/$_LP_PERCENT}"
    }
else
    echo "liquidprompt: shell not supported" >&2
    return
fi

###############
# 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

# Extended regexp patterns for sed
# GNU/BSD sed
_LP_SED_EXTENDED=r
[[ "$LP_OS" = Darwin ]] && _LP_SED_EXTENDED=E

#################
# CONFIGURATION #
#################

# Load the user configuration and setup defaults.
__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_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_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_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_ENABLE_PERM=${LP_ENABLE_PERM:-1}
    LP_ENABLE_SHORTEN_PATH=${LP_ENABLE_SHORTEN_PATH:-1}
    LP_ENABLE_PROXY=${LP_ENABLE_PROXY:-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_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_SCLS=${LP_ENABLE_SCLS:-1}
    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_SSH_COLORS=${LP_ENABLE_SSH_COLORS:-0}
    LP_ENABLE_FQDN=${LP_ENABLE_FQDN:-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_DIRSTACK=${LP_ENABLE_DIRSTACK:-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_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_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_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_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_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_DIRSTACK=${LP_COLOR_DIRSTACK:-$BOLD_YELLOW}

    if [[ -z "${LP_COLORMAP-}" ]]; 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

    # 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
            source "$configfile"
            break
        fi
    done

    # Deprecations and compatability 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
}

# Initialize features based on the user config.
lp_activate() {
    if (( _LP_SHELL_bash )); then
        # Disable the DEBUG trap used by the RUNTIME feature
        # (in case we are reloading LP in the same shell after disabling
        # the feature in .liquidpromptrc)
        (( ${LP_ENABLE_RUNTIME-0} || ${LP_ENABLE_RUNTIME_BELL-0} )) && trap - DEBUG

        complete -F __lp_theme_bash_complete lp_theme
    else # zsh
        # For ZSH, autoload required functions
        autoload -Uz add-zsh-hook

        # 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 precmd  __lp_runtime_after
        } >/dev/null

        # Enable the autocomplete if the autocomplete system is initialized.
        __lp_is_function compdef && compdef __lp_theme_zsh_complete lp_theme
    fi

    # 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()
    {
        (( LP_ENABLE_$1 )) && { command -v "$2" >/dev/null || eval LP_ENABLE_$1=0 ; }
    }

    _lp_require_tool GIT git
    _lp_require_tool SVN svn
    _lp_require_tool FOSSIL fossil
    _lp_require_tool HG hg
    _lp_require_tool BZR bzr

    _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
    else
        _lp_require_tool BATT acpi
    fi

    unset -f _lp_require_tool

    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'
        # "\e\" but on bash \ must be escaped
        LP_TITLE_CLOSE=$'\E'"$_LP_BACKSLASH"
    else
        LP_TITLE_OPEN=$'\E]0;'
        LP_TITLE_CLOSE=$'\a'
    fi

    [[ "_${TERM-}" == _linux* ]] && LP_ENABLE_TITLE=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
            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])' \
            || LP_ENABLE_SUDO=0
        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_COLOR_MARK=$LP_COLOR_MARK_ROOT
        LP_COLOR_PATH=$LP_COLOR_PATH_ROOT
        LP_COLOR_USER=$LP_COLOR_USER_ROOT
    fi

    #################
    # Where are we? #
    #################

    _LP_RUNTIME_LAST_SECONDS=$SECONDS

    if (( LP_ENABLE_RUNTIME || LP_ENABLE_RUNTIME_BELL)); then
        if (( _LP_SHELL_zsh )); then
            add-zsh-hook preexec __lp_runtime_before
            add-zsh-hook precmd  __lp_runtime_after
        else
            _LP_AT_PROMPT=0
            # __lp_runtime_before 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_runtime_before "$_"' DEBUG
        fi
    fi

    if (( LP_ENABLE_TEMP )); then
        # Try each _lp_temp method
        # If no function worked, disable the feature
        __lp_temp_detect acpi sensors || 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"
        liquidprompt_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
_lp_as_text() {
    # Remove all terminal sequences that we wrapped with $_LP_OPEN_ESC and
    # $_LP_CLOSE_ESC.
    printf '%s' "$1" | sed "-$_LP_SED_EXTENDED" "s,$_LP_CLEAN_ESC,,g"
}

# 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]}"
}

# 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 subsitute 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>_theme_prompt().
__lp_theme_list() {
    lp_theme_list=()

    local -a _functions
    if (( _LP_SHELL_zsh )); then
        _functions=( "${(ko)functions[@]}" )
    else
        local IFS=$'\n'
        _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
}

##########################
# 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 lp_pwd_tilde
    __lp_pwd_tilde
    local display_path=$lp_pwd_tilde

    local -i path_length=${#display_path}

    local lp_vcs_root lp_vcs_type
    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 == 1 || $LP_PATH_METHOD == "truncate_to_last_dir" ]]; then
        if [[ $path_length > 1 ]]; then
            lp_path=${display_path##*/}
        else
            # only root or home to show
            lp_path=$display_path
        fi

        if [[ $display_path == $vcs_root_directory ]]; then
            lp_path_format="${vcs_root_format}${lp_path}"
        else
            lp_path_format="${last_directory_format}${lp_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} > 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_path_format+="${vcs_root_format}${current_directory}"
        elif [[ -z $path_to_proccess ]]; then
            __lp_end_path_left_shortening
            # Last directory
            lp_path+=$current_directory
            lp_path_format+="${last_directory_format}${current_directory}"
        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_path_format+="${shortened_directory_format}${lp_unique_directory}"
                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_path_format+="${shortened_directory_format}${shortened_path}"

                    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_path_format+="${shortened_directory_format}${shortened_path}"
                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_path_format+="${shortened_directory_format}${shortened_path}"
                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_path_format+="${path_format}${current_directory}"
            fi
        else
            __lp_end_path_left_shortening
            lp_path+=$current_directory
            lp_path_format+="${path_format}${current_directory}"
        fi

        if [[ -n $path_to_proccess && ( $current_path != "/" || $separator != "/" ) ]] && (( ! is_shortening )); then
            if [[ $current_path != "/" ]]; then
                lp_path+="/"
            fi
            lp_path_format+="${separator_format}${separator}"
        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
    fi

    # tmux: see GH #304
    # TODO check on *BSD
    local whoami="$(LC_ALL=C who am i)"
    if [[ x"$whoami" != *'('* || x"$whoami" = *'(:'* || x"$whoami" = *'(tmux'* ]]; then
        lp_connection=lcl  # Local
        return
    fi

    local sess_parent="$(ps -o comm= -p "$PPID" 2> /dev/null)"
    if [[ "$sess_parent" = "su" || "$sess_parent" = "sudo" ]]; then
        lp_connection=su   # Remote su/sudo
    else
        lp_connection=tel  # Telnet
    fi
}

_lp_chroot() {
    if [[ -r /etc/debian_chroot ]]; then
        IFS= read -r lp_chroot </etc/debian_chroot
        if [[ -n "$lp_chroot" ]]; then
            local ret
            __lp_escape "$lp_chroot"
            lp_chroot=$ret
            return 0
        fi
    fi
    return 1
}

_lp_error() {
    (( LP_ENABLE_ERROR )) || return 2

    (( lp_error != 0 ))
}

_lp_error_color() {
    _lp_error || return "$?"

    lp_error_color="${LP_COLOR_ERR}${lp_error}${NO_COL}"
}

_lp_multiplexer() {
    if [[ -n ${TMUX-} ]]; then
        lp_mulitplexer=tmux
        return 0
    elif [[ "${TERM-}" == screen* ]]; then
        lp_mulitplexer=screen
        return 0
    fi
    return 1
}

_lp_http_proxy() {
    (( LP_ENABLE_PROXY )) || return 2

    if [[ -n "${http_proxy-}${HTTP_PROXY-}${https_proxy-}${HTTPS_PROXY-}${all_proxy-}${ALL_PROXY-}" ]]; then
        local ret
        __lp_escape "${http_proxy:-${HTTP_PROXY:-${https_proxy:-${HTTPS_PROXY:-${all_proxy:-$ALL_PROXY}}}}}"
        lp_http_proxy=$ret
    else
        return 1
    fi
}

_lp_http_proxy_color() {
    _lp_http_proxy || return "$?"

    lp_http_proxy_color="${LP_COLOR_PROXY}${LP_MARK_PROXY}${NO_COL}"
}

_lp_python_env() {
    (( LP_ENABLE_VIRTUALENV )) || return 2

    local ret

    # Why are these trunkated to the last '/' section? It has always been this
    # way, and the original author didn't explain why.
    if [[ -n "${VIRTUAL_ENV-}" ]]; then
        __lp_escape "${VIRTUAL_ENV##*/}"
        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() {
    _lp_python_env || return "$?"

    lp_python_env_color="[${LP_COLOR_VIRTUALENV}${lp_python_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() {
    _lp_software_collections || return "$?"

    lp_software_collections_color="[${LP_COLOR_VIRTUALENV}${lp_software_collections}${NO_COL}]"
}

# 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
        lp_username=''
        return 2
    elif (( LP_USER_ALWAYS )) || ! _lp_user; then
        lp_username=${_LP_USER_SYMBOL}
        return 0
    else
        lp_username=''
        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
_lp_sudo_active() {
    (( LP_ENABLE_SUDO )) || return 2
    \sudo -n true 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

        # Which host symbol should we use?
        if (( LP_ENABLE_FQDN )); then
            LP_HOST_SYMBOL="${_LP_FQDN_SYMBOL}"
        else
            LP_HOST_SYMBOL="${_LP_HOST_SYMBOL}"
        fi

        lp_hostname=${LP_HOST_SYMBOL}

        _lp_connection
        if [[ $lp_connection == lcl ]] && ! (( LP_HOSTNAME_ALWAYS )); then
            # no hostname if local
            return 1
        fi
    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)
            # 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}${lp_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}"
}

################
# 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 -c '[Dd]etach[^)]*)$')
    (( _LP_ENABLE_TMUX )) && count+=$(tmux list-sessions 2> /dev/null | \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_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_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

    local vcs

    lp_vcs_root="$PWD"
    while [[ -n "$lp_vcs_root" ]]; do
        for vcs in ${_LP_ENABLED_VCSS[@]+"${_LP_ENABLED_VCSS[@]}"}; do
            if [[ -d "$lp_vcs_root/.$vcs" ]]; then
                lp_vcs_type="$vcs"
                return 0
            fi
        done

        if (( LP_ENABLE_GIT )) && [[ -f "$lp_vcs_root/.git" ]]; then
            lp_vcs_type="git"
            return 0
        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

    lp_vcs_type=
    lp_vcs_root=
    return 1
}

# Set the prompt mark depending on the current VCS.
_lp_smart_mark() {
    case "${1:-$lp_vcs_type}" in
    git)
        lp_smart_mark="$LP_MARK_GIT"

        if [[ -n "${VCSH_DIRECTORY-}" ]]; then
            lp_smart_mark="$LP_MARK_VCSH$LP_MARK_GIT$LP_MARK_VCSH"
        elif [[ -d "${lp_vcs_root}/.git/svn" ]]; then
            lp_smart_mark="$LP_MARK_GIT$LP_MARK_SVN"
        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 Liquidprompt and the current
# directory is a valid repository of that type.
_lp_vcs_active() {
    "_lp_${lp_vcs_type}_active" 2>/dev/null
}

# Get the branch name of the repo in the current directory.
_lp_vcs_branch() {
    "_lp_${lp_vcs_type}_branch" 2>/dev/null
}

# Get the bookmark name of the repo in the current directory.
_lp_vcs_bookmark() {
    "_lp_${lp_vcs_type}_bookmark" 2>/dev/null
}

# Get a tag name of the repo in the current directory.
_lp_vcs_tag() {
    "_lp_${lp_vcs_type}_tag" 2>/dev/null
}

# Get the current commit string for the repo in the current directory.
_lp_vcs_commit_id() {
    "_lp_${lp_vcs_type}_commit_id" 2>/dev/null
}

# Get additional information if the repo is in a special or unusual state.
_lp_vcs_head_status() {
    "_lp_${lp_vcs_type}_head_status" 2>/dev/null
    # 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" 2>/dev/null
}

# Get the number of commits ahead and behind the upstream branch.
_lp_vcs_commits_off_remote() {
    "_lp_${lp_vcs_type}_commits_off_remote" 2>/dev/null
}

# Get the number of untracked aka extra files in the repo.
_lp_vcs_untracked_files() {
    "_lp_${lp_vcs_type}_untracked_files" 2>/dev/null
}

# Get the number of changed files compared to the last or checked out commit.
_lp_vcs_uncommitted_files() {
    "_lp_${lp_vcs_type}_uncommitted_files" 2>/dev/null
}

# Get the number of changed lines compared to the last or checked out commit.
_lp_vcs_uncommitted_lines() {
    "_lp_${lp_vcs_type}_uncommitted_lines" 2>/dev/null
}

# Get the number of changed files compared to staging.
_lp_vcs_unstaged_files() {
    "_lp_${lp_vcs_type}_unstaged_files" 2>/dev/null
}

# Get the number of changed lines compared to staging.
_lp_vcs_unstaged_lines() {
    "_lp_${lp_vcs_type}_unstaged_lines" 2>/dev/null
}

# 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" 2>/dev/null
}

# 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" 2>/dev/null
}

# GIT #

# Check if Git is enabled in Liquidprompt 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
}

# 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 gitdir="${lp_vcs_root}/.git" IFS= step total

    if [[ -f "${gitdir}/MERGE_HEAD" ]]; then
        lp_vcs_head_status="MERGING"
    elif [[ -d "${gitdir}/rebase-merge" ]]; then
        read step <"${gitdir}/rebase-merge/msgnum"
        read total <"${gitdir}/rebase-merge/end"
        if [ -f "${gitdir}/rebase-merge/interactive" ]; then
            lp_vcs_head_status="REBASE-i"
        else
            lp_vcs_head_status="REBASE-m"
        fi
    elif [[ -d "${gitdir}/rebase-apply" ]]; then
        read step <"${gitdir}/rebase-apply/next"
        read total <"${gitdir}/rebase-apply/last"
        if [ -f "${gitdir}/rebase-apply/rebasing" ]; then
            lp_vcs_head_status="REBASE"
        elif [ -f "${gitdir}/rebase-apply/applying" ]; then
            lp_vcs_head_status="AM"
        else
            lp_vcs_head_status="AM/REBASE"
        fi
    elif [[ -f "${gitdir}/CHERRY_PICK_HEAD" ]]; then
        lp_vcs_head_status="CHERRY-PICKING"
    elif [[ -f "${gitdir}/REVERT_HEAD" ]]; then
        lp_vcs_head_status="REVERTING"
    elif [[ -f "${gitdir}/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 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 -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 "$?"

    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 "$?"

    lp_vcs_staged_files=$lp_git_diff_shortstat_files
}

# Get the number of changed lines in staging compared to HEAD.
_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 #
# Note that Mercurial has no staging area

# Check if Mercurial is enabled in Liquidprompt and the current directory is a
# valid Mercurial repository.
_lp_hg_active() {
    (( LP_ENABLE_HG )) || return 2
    \hg 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="$(\hg 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="$(\hg 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="$(\hg 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="$(\hg identify --id 2>/dev/null)"
}

# Get additional information if the repo is in any unfinished state.
_lp_hg_head_status() {
    local hgdir="${lp_vcs_root}/.hg"
    if [[ -d "${hgdir}/merge" ]]; then
        lp_vcs_head_status="MERGING"
    elif [[ -f "${hgdir}/rebasestate" ]]; then
        lp_vcs_head_status="REBASING"
    elif [[ -f "${hgdir}/updatestate" ]]; then
        lp_vcs_head_status="UPDATING"
    elif [[ -f "${hgdir}/bisect.state" ]]; then
        lp_vcs_head_status="BISECTING"
    elif [[ -f "${hgdir}/shelvedstate" ]]; then
        lp_vcs_head_status="SHELVING"
    elif [[ -f "${hgdir}/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="$(\hg 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 (3: disabled).
_lp_hg_commits_off_remote() {
    #commits=$(\hg outgoing --no-merges 2>/dev/null | \grep -c '\(^changeset\:\)')
    return 3
}

# Get the number of untracked files in the Mercurial repo.
_lp_hg_untracked_files() {
    local untracked
    untracked="$(\hg 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="$(\hg 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 lp_vcs_uncommitted_i_lines lp_vcs_uncommitted_d_lines \
        <<<"$(\hg 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 ))
}

# SUBVERSION #
# Note that Subversion has no tags, stashes, or staging area. It also has no
# concept of a remote, since it is not distributed and always must be in sync.

# Check if Subversion is enabled in Liquidprompt 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
}

# 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)"
}

# 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 -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 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 ))
}

# FOSSIL #
# Note that Fossil has no staging area, bookmarks, or unique tags.

# Check if Fossil is enabled in Liquidprompt 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
}

# 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 ))
}

# 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 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 ))
}

# Bazaar #
# Note that Bazaar has no staging area, bookmarks, remote tracking branches, or extra statuses.

# Check if Bazaar is enabled in Liquidprompt 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
}

# Get the most recent tag that refers to the current revision.
_lp_bzr_tag() {
    local tag ret eol
    IFS=$' \t' read tag eol <<<"$(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)"
}

# 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 ))
}

# 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 -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 -vc '^?')"

    (( lp_vcs_uncommitted_files ))
}

# Get the number of changed lines compared to the checked-out version.
_lp_bzr_uncommitted_lines() {
    IFS=' ' read 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 ))
}

####################
# Wifi link status #
####################

# TODO: test/fix/implement this
_lp_wifi() {
    # Linux
    sed -n '3s/^ *[^ ]*  *[^ ]*  *\([0-9]*\).*/\1/p' /proc/net/wireless
}

##################
# Battery status #
##################

# Get the battery status in percent.
case "$LP_OS" in
    Linux)
    _lp_battery() {
        (( LP_ENABLE_BATT )) || return 5
        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 '%'

        if [[ -z "${lp_battery}" ]]; then
            # no battery level found
            return 4
        fi
        # discharging
        if [[ "$acpi" == *"Discharging"* ]]; then
            # under => 0, above => 1
            return "$(( lp_battery > LP_BATTERY_THRESHOLD ))"
        # not charging
        elif [[ "$acpi" == *"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 percent 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
}

if (( _LP_SHELL_zsh )); then
    __lp_runtime_before() {
      _LP_RUNTIME_LAST_SECONDS=$SECONDS
    }
else
    __lp_runtime_before() {
        # For debugging
        #echo "XXX $BASH_COMMAND"

        # If this is the first time after the user submitted the command,
        # record the time
        if (( _LP_AT_PROMPT )); then
            _LP_RUNTIME_SECONDS=-1 _LP_RUNTIME_LAST_SECONDS=$SECONDS _LP_AT_PROMPT=0
        fi
        # If this is when the prompt is being drawn, the command is done,
        # so calculate the time. Note these two events could be at the same
        # time, so no elif is used
        if [[ "$BASH_COMMAND" == "${PROMPT_COMMAND-}" ]]; then
            _LP_AT_PROMPT=1
            __lp_runtime_after
        fi
    }
fi

# 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 -c '^[Pp]rocessor' /proc/cpuinfo )
        }
        _lp_cpu_load () {
            local eol IFS=$' \t'
            read lp_cpu_load eol < /proc/loadavg
        }
        ;;
    FreeBSD|Darwin|OpenBSD)
        __lp_cpu_count() {
            _lp_CPUNUM=$( sysctl -n hw.ncpu )
        }
        _lp_cpu_load () {
            local bol eol 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 bol lp_cpu_load eol <<<"$( LC_ALL=C sysctl -n vm.loadavg )"
        }
        ;;
    SunOS)
        __lp_cpu_count() {
            _lp_CPUNUM=$( kstat -m cpu_info | \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.

# 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

    # Global variable
    unset _LP_TEMP_FUNCTION

    for cmd
    do
        command -v "$cmd" >/dev/null || continue

        _LP_TEMP_FUNCTION=__lp_temp_$cmd
        # 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
        unset _LP_TEMP_FUNCTION
    done
    return 1
}

# Returns current highest system temperature.
_lp_temperature() {
    (( LP_ENABLE_TEMP )) || return 2

    "$_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
    printf '%s' "${_LP_OPEN_ESC}${LP_TITLE_OPEN}"
    _lp_as_text "${1-}"
    printf '%s' "${LP_TITLE_CLOSE}${_LP_CLOSE_ESC}"
}

_lp_formatted_title() {
    (( LP_ENABLE_TITLE )) || return 2

    # Get the input as pure text
    _lp_generated_title=$(_lp_as_text "${1-}")
}

_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
}

###################
# 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 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

    lp_time=${_LP_TIME_SYMBOL}
}

_lp_time_color() {
    _lp_time || return "$?"

    lp_time_color="${LP_COLOR_TIME}${_LP_TIME_SYMBOL}${NO_COL}"
}

#################
# 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_BRACKET_OPEN}${NO_COL}"
        LP_BRACKET_CLOSE="${LP_COLOR_IN_MULTIPLEXER}${LP_MARK_BRACKET_CLOSE}${NO_COL}"
    else
        LP_BRACKET_OPEN="${LP_MARK_BRACKET_OPEN}"
        LP_BRACKET_CLOSE="${LP_MARK_BRACKET_CLOSE}"
    fi

    _lp_terminal_device
    LP_TTYN=$lp_terminal_device

    if _lp_time_color; then
        LP_TIME="${lp_time_color} "
    else
        LP_TIME=
    fi
}

_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 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}"
}

_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_analog_time_color; then
        LP_TIME="$lp_analog_time_color "
    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_python_env_color; then
        LP_VENV=" $lp_python_env_color"
    else
        LP_VENV=
    fi

    if _lp_software_collections_color; then
        LP_SCLS=" $lp_software_collections_color"
    else
        LP_SCLS=
    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_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
        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_JOBS}"
        # add user, host and permissions colon
        PS1+="${LP_BRACKET_OPEN}${LP_USER}${LP_HOST}${LP_PERM}"

        PS1+="${LP_PWD}${LP_DIRSTACK}${LP_BRACKET_CLOSE}${LP_SCLS}${LP_VENV}${LP_PROXY}"

        # 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_MARK_PREFIX}${LP_COLOR_MARK}${LP_MARK}${LP_PS1_POSTFIX}"

        # Get the current prompt on the fly and make it a title
        _lp_formatted_title "$PS1"

        # Glue the bash prompt always go to the first column.
        # Avoid glitches after interrupting a command with Ctrl-C
        # Does not seem to be necessary anymore?
        #PS1="\[\033[G\]${PS1}${NO_COL}"
    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="$?"

    #TODO: unused?
    local GREP_OPTIONS=

    # bash: execute the old prompt hook
    eval "$LP_OLD_PROMPT_COMMAND"

    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
        PS1="${_LP_OPEN_ESC}${LP_TITLE_OPEN}${_lp_manual_title:-${_lp_generated_title-${SHELL-}}}${LP_TITLE_CLOSE}${_LP_CLOSE_ESC}${PS1}"
    fi
}

liquidprompt_tag() {
    if [[ -n "${1-}" ]]; then
        export LP_PS1_PREFIX="$1 "
    else
        export LP_PS1_PREFIX=
    fi
}

# Activate Liquid Prompt
liquidprompt_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"
        if (( _LP_SHELL_bash )); then
            LP_OLD_PROMPT_COMMAND="${PROMPT_COMMAND-}"
            _LP_OLD_SHOPT="$(shopt -p promptvars)"
        else # zsh
            LP_OLD_PROMPT_COMMAND=""
            _LP_ZSH_PROMPT_THEME=""
            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
            _LP_OLD_SETOPT=()
            # Dump option names: echo ${(ko)options}
            for o in promptpercent promptbang promptsubst
            do
                if [[ "${options[$o]}" = on ]]; then
                    _LP_OLD_SETOPT+=("$o")
                else
                    _LP_OLD_SETOPT+=("no$o")
                fi
            done
        fi
    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

        # Disable parameter/command expansion from PS1
        shopt -u promptvars
        PROMPT_COMMAND=__lp_set_prompt
        (( LP_DEBUG_TIME )) && PROMPT_COMMAND="time $PROMPT_COMMAND" || true
    else # zsh
        [[ -n "${_LP_ZSH_HOOK-}" ]] && add-zsh-hook -d precmd "$_LP_ZSH_HOOK"
        # Set options that affect PS1 evaluation
        # Disable parameter/command expansion; enable percent expansion
        setopt promptpercent nopromptbang nopromptsubst
        # 'time' doesn't seem to work on shell functions: no time output
        #if (( LP_DEBUG_TIME )); then
        #    _lp_main_precmd() {
        #        local TIMEFMT='Liquid Prompt build time: %*E'
        #        time __lp_set_prompt
        #    }
        #    _LP_ZSH_HOOK=_lp_main_precmd
        #else
            _LP_ZSH_HOOK=__lp_set_prompt
        #fi
        add-zsh-hook precmd "$_LP_ZSH_HOOK"
    fi
}

# Come back to the old prompt
liquidprompt_off() {
    PS1=$LP_OLD_PS1
    if (( _LP_SHELL_bash )); then
        eval "$_LP_OLD_SHOPT"
        PROMPT_COMMAND="$LP_OLD_PROMPT_COMMAND"
    else # zsh
        add-zsh-hook -d precmd "$_LP_ZSH_HOOK"
        setopt "${_LP_OLD_SETOPT[@]}"
        (( ${#_LP_ZSH_PROMPT_THEME} )) && prompt "$_LP_ZSH_PROMPT_THEME"
    fi
}

# Use an empty prompt: just the \$ mark
liquidprompt_OFF() {
    PS1="$_LP_MARK_SYMBOL "
    if (( _LP_SHELL_bash )); then
        shopt -u promptvars
        PROMPT_COMMAND="$LP_OLD_PROMPT_COMMAND"
    else # zsh
        add-zsh-hook -d precmd "$_LP_ZSH_HOOK"
        setopt promptpercent nopromptbang nopromptsubst
    fi
}

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 Liquidprompt 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"
    liquidprompt_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:
