How to accomplish a “responsive” bash prompt

I have always liked having my full path in my bash prompt, and also (when applicable) my current git branch. This way I always have the proper context I need to be able to navigate quickly and prevent myself from having to constantly type pwd to figure out where I am.

This works well until you’re working in a smaller terminal window and are deep within a project. For one reason or another you’re down to 80-100 columns, and your prompt is taking up the entire width of the terminal (and then some).

I also have attempted to use the 2 line prompt where the info is on the first line, leaving your commands to be entered on the next line, but I find this really annoying. If you’re in the same boat as me, let me save you the hours of work on something so trivial.

The responsive prompt in action

I have my prompt set up to switch at 110 columns to a more concise layout. At more than 110 columns, it looks like this.

Full Prompt

When I go below the terminal width of 110 columns I wanted to streamline it, while still providing relevant info that will help me get 90% of the information I get from the full prompt. This is what I ended up with.

Responsive Prompt

Currently it checks the terminal width every time a new prompt is rendered, so reloading your bash environment is not necessary.

Extra

Another neat thing I recently did with my prompt is change the color of the arrow to red if the previous command returns an exit status other than zero.

Command Failure

The Code

If you’re new to bash prompt customization, please at least skim through this before continuing. It provides the basics of prompt customization, this is a more advanced customization.

First I reconstructed the \w prompt variable that produces the current working directory (with the ~ home directory replacement), with this function.

working_directory() {
    dir=`pwd`
    in_home=0
    if [[ `pwd` =~ ^$HOME($|\/) ]]; then
        dir="~${dir#$HOME}"
        in_home=1
    fi

    if [[ `tput cols` -lt 110 ]]; then  # <-- Checking the term width
        first="/`echo $dir | cut -d / -f 2`"
        letter=${first:0:2}
        if [[ $in_home == 1 ]]; then
            letter="~$letter"
        fi
        proj=`echo $dir | cut -d / -f 3`
        beginning="$letter/$proj"
        end=`echo "$dir" | rev | cut -d / -f1 | rev`

        if [[ $proj == "" ]]; then
            echo $dir
        elif [[ $proj == "~" ]]; then
            echo $dir
        elif [[ $dir =~ "$first/$proj"$ ]]; then
            echo $beginning
        elif [[ $dir =~ "$first/$proj/$end"$ ]]; then
            echo "$beginning/$end"
        else
            echo "$beginning/…/$end"
        fi
    else
        echo $dir
    fi
}

Then to abbreviate the git branch I wrote this function…

parse_git_branch() {
    if [[ -f /usr/local/etc/bash_completion.d/git-completion.bash ]]; then
        branch=`__git_ps1 "%s"`
    else
        ref=$(git-symbolic-ref HEAD 2> /dev/null) || return
        branch="${ref#refs/heads/}"
    fi

    if [[ `tput cols` -lt 110 ]]; then  # <---- Again checking the term width
        branch=`echo $branch | sed s/feature/f/1`
        branch=`echo $branch | sed s/hotfix/h/1`
        branch=`echo $branch | sed s/release/\r/1`
        branch=`echo $branch | sed s/master/mstr/1`
        branch=`echo $branch | sed s/develop/dev/1`
    fi

    if [[ $branch != "" ]]; then
        echo "$branch "
    fi
}

Then we wrap it all up into a prompt function that I assign to PROMPT_COMMAND which gets executed before every time the prompt is rendered. This is where the exit status checking happens.

prompt() {
    if [[ $? -eq 0 ]]; then
        exit_status='\[\e[0;34m\]› \[\e[00m\]'
    else
        exit_status='\[\e[0;31m\]› \[\e[00m\]'
    fi

    prompt='\[\e[0;33m\]$(working_directory)\[\e[00m\]\[\e[0;32m\] $(parse_git_branch)\[\e[00m\]'
    PS1=$prompt$exit_status
}
PROMPT_COMMAND=prompt

And vioala, your prompt is now smart enough to be more conservative with smaller terminal widths.

Follow me on Twitter ( ) or subscribe via RSS ( ).