Terminal tricks from my dotfiles
Tags: tutorial, productivity, showdev, shell
Published: 2021-04-13 15:10:27

I work with a terminal every day. Being able to move around and get things done quickly is a huge boost to my performance and saves me a lot of time. Over the years my personal terminal configuration has evolved to include some neat tricks that I haven't seen many people use.

In case you're not familiar with them: [dotfiles](https://thoughtbot.com/upcase/videos/intro-to-dotfiles) are a way of managing the many usually hidden configuration files found in your home directory (most of their filenames begin with a dot). Some developers like to publicly host their dotfiles for anyone to read and use.

A dotfiles repository usually includes a bunch of configuration files and some sort of shell script to copy or symlink them from the repository folder to a user's home directory. It's helpful to have some experience with shell scripting to get into dotfiles, but if you have done any type of programming you'll probably be able to pick up shell scripting or follow along.

Everything in this article comes from [my personal dotfiles repository](https://github.com/adamhollett/dotfiles). Most of these are built for zsh but some may work in bash as well — as with any code snippet on the web, your mileage may vary.

## Seasonal chevrons

The crown jewel of my dotfiles repo is the set of colourful chevrons at the end of my prompt:

![A terminal window showing three multicoloured arrows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7q1lmb0udkai5f5h3is.gif)

The chevrons are purely decorative, but every three months they change colours to match the season. I always forget about this feature, which means that once every three months I open my terminal and get a little pang of delight when I see that they've changed again.

### How it works

Here's the zsh function that returns the chevrons:

~~~ zsh
seasonal_chevrons () {
  local date=$(date)
  local chevrons="❯❯❯"

  case $date in
    # spring
    *Mar*|*Apr*|*May*)
      chevrons="%F{cyan}❯%F{green}❯%F{yellow}❯%f"
      ;;
    # summer
    *Jun*|*Jul*|*Aug*)
      chevrons="%F{green}❯%F{yellow}❯%F{red}❯%f"
      ;;
    # fall
    *Sep*|*Oct*|*Nov*)
      chevrons="%F{yellow}❯%F{red}❯%F{magenta}❯%f"
      ;;
    # winter
    *Dec*|*Jan*|*Feb*)
      chevrons="%F{magenta}❯%F{cyan}❯%F{green}❯%f"
      ;;
    *)
      ;;
  esac

  echo -en $chevrons
}
~~~

Then we call the function when exporting the `PS1` (prompt) variable in `.zshrc`:

~~~ zsh
export PS1='$(seasonal_chevrons) '
~~~

At the beginning of the function, we assign the date and a set of chevrons as variables. In case our function doesn't work, we'll return those uncoloured chevrons.

The function reads the date and looks for what month it is, in sets of three. For example, `*Mar*|*Apr*|*May*` means to match "March OR April OR May". When it finds a match, it reassigns the `chevrons` variable, colouring them using zsh colour keywords.

At the end of the function, we `echo` whatever we ended up with.

## Dynamic git identity

Sometimes I use my terminal for personal work, like this article. In those cases, I like my git commits to use my personal email address and not my work email.

Luckily, all the repos that I use for work have the company name in their path, so I set up a dynamic include in my `.gitconfig` that overrides my configured email address for those directories. It looks like this:

~~~ git
[user]
  name = Adam Hollett
  email = adamh@example.com

[includeIf "gitdir/i:**/workcompany/**"]
  email = adamh@workcompany.com
~~~

The `includeIf` directive in `.gitconfig` only activates the configuration in that category if its pattern is matched. In this case, it looks for the name `workcompany` in the current path and sets a work email address if it finds it.

## Sort git branches by recency

When you type `git branch`, your branch list is sorted alphabetically by default. This isn't super helpful. To sort your branches by their last commit date, with the most recent at the top, add this to your `.gitconfig`:

~~~ git
[branch]
  sort = -committerdate
~~~

## The git status dot

The command I use most often is probably `git status`. This lets me check where I am in the process of writing and committing code and which files I've changed.

Instead of having to type this command over and over again, I wrote some functions to display a "status dot" for four different states:

- solid green for a "clean" state with no changes:

  ![A terminal prompt featuring a solid green circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dy4twpo1y7botdxenrn.png)

- hollow purple if any tracked files have been changed:

  ![A terminal prompt featuring a hollow purple circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ofqhu3vu57m551c1t8w.png)

- shaded yellow if changes have been staged for a commit:

  ![A terminal prompt featuring a shaded yellow circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zbwpa05t5mqwvruocia.png)

- and solid blue if we have any commits ahead of the remote branch:

  ![A terminal prompt featuring a solid blue circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7fu9hoy0aiomrnmby01.png)

This dot only appears if the current path is a git repo, which has the added bonus of telling me whether I'm currently in a repo.

### How it works

There are a few moving parts to this one. First we have a function called `git_check` that checks whether we're in a git repo by testing the contents of the command `git branch`:

~~~ zsh
# Return the branch name if we're in a git repo, or nothing otherwise.
git_check () {
  local gitBranch=$(git branch 2> /dev/null | sed -e "/^[^*]/d" -e "s/* \(.*\)/\1/")
  if [[ $gitBranch ]]; then
    echo -en $gitBranch
    return
  fi
}
~~~

To be honest I don't exactly know how this works. But if we're in a git repo it outputs the branch name, otherwise it does nothing.

Another function checks the message output by `git status` and gives back a state name:

~~~ zsh
# Return the status of the current git repo.
git_status () {
  local gitBranch="$(git_check)"
  if [[ $gitBranch ]]; then
    local statusCheck=$(git status 2> /dev/null)
    if [[ $statusCheck =~ 'Your branch is ahead' ]]; then
      echo -en 'ahead'
    elif [[ $statusCheck =~ 'Changes to be committed' ]]; then
      echo -en 'staged'
    elif [[ $statusCheck =~ 'no changes added' ]]; then
      echo -en 'modified'
    elif [[ $statusCheck =~ 'working tree clean' ]]; then
      echo -en 'clean'
    fi
  fi
}
~~~

I use these functions in another `git_dot` function. You can see at the beginning here that if `$gitCheck` does not get assigned then most of the body of this function is skipped — there's no need to do any of this if we're not currently in a git repo:

~~~ zsh
# Print a dot indicating the current git status.
git_dot () {
  local gitCheck="$(git_check)"
  if [[ $gitCheck ]]; then
    local gitStatus="$(git_status)"
    local gitStatusDot='●'
    if [[ $gitStatus == 'staged' ]]; then
      local gitStatusDot='◍'
    elif [[ $gitStatus == 'modified' ]]; then
      local gitStatusDot='○'
    fi
    if [[ $gitCheck && ! $gitCheck == 'master' && $COLUMNS -lt 100 ]]; then
      echo -en "%F{#616161}⌥%f "
    fi
    echo -en "%F{"$(git_status_color)"}$gitStatusDot%f "
  fi
}
~~~

At the end of the above function we invoke another function called `git_status_color` to apply a colour to the dot based on the current git status message, using the same `git_status` function as before:

~~~ zsh
# Return a color based on the current git status.
git_status_color () {
  local gitStatus="$(git_status)"
  local statusText=''
  case $gitStatus in
    clean*)
      statusText="green"
      ;;
    modified*)
      statusText="magenta"
      ;;
    staged*)
      statusText="yellow"
      ;;
    ahead*)
      statusText="cyan"
      ;;
    *)
      statusText="white"
      ;;
  esac
  echo -en $statusText
}
~~~

Looking at this now, I can see that there's a lot of refactoring that could be done here. This doesn't necessarily need four separate functions. One of the great things about writing and maintaining your own dotfiles is being able to learn and improve as you get more practice.

## git start

Here's a quick one: a git alias to return you to the master branch, pull the latest changes, and clean up any stray files. Add this to your `.gitconfig`:

~~~ git
[alias]
  start = !git checkout master && git pull && git clean -fd
~~~

Then type `git start` anywhere to run all three commands.

## Responsiveness

I got involved in front-end development right around the time that responsive design was taking over the web. With responsive design, the contents of a web application change to fit the window or device it's being viewed with.

You can do the same thing with your terminal prompt by reacting to the number of columns available using the `$COLUMNS` environment variable.

You may have noticed this in the `git_dot` function in the last example:

~~~ zsh
if [[ $gitCheck && ! $gitCheck == 'master' && $COLUMNS -lt 100 ]]; then
  echo -en "%F{#616161}⌥%f "
fi
~~~

This part of the function checks the current branch name. If it is _not_ `master`, and if the number of columns available is less than 100, then it outputs a grey "option" symbol you may recognize from a Mac keyboard: ⌥. This lets us know that we are on a branch.

![A terminal prompt showing a small git branch icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pz5vi2400sitwufpp99n.png)

But if our terminal window is large enough, we can just print the branch name. zsh allows you to assign a variable `RPROMPT` to right-align part of your prompt, so I assign that in `.zshrc`:

~~~ zsh
export RPROMPT='$(git_branch)'
~~~

~~~ zsh
# Print a label for the current git branch if it isn't master.
git_branch () {
  local gitBranch="$(git_check)"
  if [[ $gitBranch && ! $gitBranch == 'master' && $COLUMNS -gt 79 ]]; then
    echo -en "%F{#616161}⌥%f %F{"$(git_status_color)"}$gitBranch%f"
  fi
}
~~~

So on larger windows we get this:

![A terminal prompt showing the full git branch name, aligned to the right](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mdmwvjdw6yl96bhsi2qg.png)

The full branch name uses the same colour as the git status dot.

## Try it yourself

There is a lot more useful stuff in my dotfiles, including my [Ruby configuration](https://github.com/adamhollett/dotfiles/tree/master/ruby), my [.editorconfig](https://github.com/adamhollett/dotfiles/blob/master/editor/.editorconfig), and [a whole bunch of aliases](https://github.com/adamhollett/dotfiles/blob/master/shell/aliases.sh).

If you're interested in building your own set of dotfiles, the best place to start is by exploring other peoples' setups. dotfiles.github.io has a [good list of example repositories](https://dotfiles.github.io/inspiration/). Fork someone else's, or start from scratch and build your own piece by piece. If you're new to shell scripting, this is a fantastic way to start.

----

Cover photo by [Arnold Francisca](https://unsplash.com/@clark_fransa?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/code?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText).