Freeing my shell of configuration frameworks

I use zsh. I used to use bash, but I saw many people talk about zsh and how much better it is. It was kind of scary to switch to something I was unfamiliar with and I had bash “just how I wanted it.” If this sounds familiar, just do it. Try zsh. I love it. This won’t be a post about that, though. For a quick overview about why zsh, check out this answer on Quora. That doesn’t cover all of it, but there’s plenty more on the internet.

For a long time I used oh-my-zsh. It was ok, but it was at times a bit slow and I kept hearing about prezto and how it was just as feature rich but faster. I made the switch to try it out and while I liked the features, I actually found it to be slower for whatever reason. I also kept getting a segfault when sourcing my zshrc which I thought was Prezto’s fault (it wasn’t). I decided that oh-my-zsh and prezto were great for starting out and feature discovery but they were too heavily pre-configured for my taste and I wanted to be more in control.

The segfault was my fault

Because of course it was. Well, not technically, but it had to do with something I added to my zshrc. In particular, there’s a known clash between zsh-autosuggestions and zsh-syntax-highlighting. You can take a look at a proposed solution here, but I found this didn’t actually fix the issue for me. In the end I just decided I valued the syntax highlighting more than the autosuggestions, and zsh-history-substring-search played a similar role to zsh-autosugestions anyway, so I got rid of autosuggestions in the meantime and can now happily source ~/.zshrc without everything breaking.

Prompt

The first thing to take care of in freeing myself of prezto was the prompt. I really like the prompt in sorin’s default prezto theme. The code for that prompt is a bit scary, but it comes packed with lots of little features and gimmicks and all I really cared for was the directory shortening (/My/long/Path/name becomes /M/l/P/name) and the git information on the right.

I eventually isolated the directory shortening to the following code:

setopt prompt_subst

function prompt_sorin_pwd {
  local pwd="${PWD/#$HOME/~}"

  if [[ "$pwd" == (#m)[/~] ]]; then
    _prompt_sorin_pwd="$MATCH"
    unset MATCH
  else
    _prompt_sorin_pwd="${${${${(@j:/:M)${(@s:/:)pwd}##.#?}:h}%/}//\%/%%}/${${pwd:t}//\%/%%}"
  fi
  echo $_prompt_sorin_pwd
}

PROMPT='%B%F{9}$(prompt_sorin_pwd) %B%F{2}❯%f%b '

If that seemed trivial to you, congrats, but it took me way too long to get that working independently of prezto. The next step was the git prompt. I found zsh-git-prompt, which looked similar enough to my old git prompt for my needs (I actually like it better now). You can install it via homebrew, but it defaults to the Python variant, which is quite a bit slower than the Haskell one, so I git clone’d the repo and built the Haskell option myself according to the instructions. It’s also customizable! Check it out:

source $HOME/Code/zsh-git-prompt/zshrc.sh
GIT_PROMPT_EXECUTABLE="haskell"

ZSH_THEME_GIT_PROMPT_BRANCH="%{$fg_bold[green]%}"
ZSH_THEME_GIT_PROMPT_CHANGED="%{%F{009}✚%G%}"
ZSH_THEME_GIT_PROMPT_AHEAD="%{%F{013}↑%G%}"
ZSH_THEME_GIT_PROMPT_BEHIND="%{%F{013}↓%G%}"
ZSH_THEME_GIT_PROMPT_CLEAN="%{%F{010}✓%G%}"
ZSH_THEME_GIT_PROMPT_STAGED="%{%F{012}●%G%}"

With all this in place, my prompt looks something like this:

Bonus: If you’re set on sticking with bash, you can get similar directory shortening with the code in this answer on stackoverflow, enabled like so:

_dir_chomp () {
    local IFS=/ c=1 n d
    local p=(${1/#$HOME/\~}) r=${p[*]}
    local s=${#r}
    while ((s>$2&&c<${#p[*]}-1))
    do
        d=${p[c]}
        n=1;[[ $d = .* ]]&&n=2
        ((s-=${#d}-n))
        p[c++]=${d:0:n}
    done
    echo "${p[*]}"
}
export PS1='\[\e[1;91m\]$(_dir_chomp "$(pwd)" 1)\[\e[92m\] ❯\[\033[00m\] '

Modules

In my zprestorc, I was loading a whole bunch of modules either because they came preconfigured, or because I didn’t really look at what it was doing. Here’s a list of everything that was being loaded (You can find the code in the modules directory). Eventually, I realized some of these were duplicates of stuff I was already loading in my zshrc, or were just adding a bunch of aliases and helper fuctions I’d never use anyway. I ended up only keeping environment, history, directory, and spectrum. I put these files in $HOME/.zmodules/ and loaded them with:

for file ($HOME/.zmodules/*.zsh(N)); do
    source $file
done

I also made sure to have a colored-man-pages module from oh-my-zsh that makes man pages look nice:

Final touches

These changes gave me a shell that was as responsive and featureful as I needed it to be. While I was at it, I found out I could autoload my custom funtions by putting them in a desired directory ($HOME/bin/zfuncs in my case), and putting the following in my zshrc:

for file ($HOME/bin/zfuncs/*(N)); do
    if [[ -a "$file" ]]; then
        autoload -Uz "${file:t}"
    fi
done

This is pretty neat, since it only loads the body of the functions on first usage, which can save you some startup time.

I also set up the help function on zsh with help from the arch wiki (a treasure trove of information, by the way):

autoload -Uz run-help
if [ "alias" = $(whence -w run-help | sed 's/run-help: //') ]; then
    unalias run-help
fi
alias help=run-help

And I have the following completion options set for colored completion and candidate selection:

zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' menu select

I’ve probably rambled far too long now, but if you’d like to know more, you can see my full zsh configuration here, since I keep it (and the rest of my dotfiles) in an org file that exports everything to the proper location on save.


  « »