Z shell configuration


ZDOTDIR

By default $ZDOTDIR is set to $HOME. I want it to be in $XDG_CONFIG_HOME.

ZDOTDIR=$HOME/.config/zsh

Prompt

TODO https://grml.org/zsh/

Just a simple prompt with only the essentials:

export PS1="%c ❯ "

Pure

target=$XDG_CONFIG_HOME/zsh/pure
[ -d $target/.git ] && exit 0
mkdir -p $target
git clone https://github.com/sindresorhus/pure.git $target
fpath+=($XDG_CONFIG_HOME/zsh/pure)

PURE_GIT_PULL=0

autoload -U promptinit; promptinit
prompt pure

prompt_newline=$(echo -n "\u200B")

TODO VCS info

Borrowed from vincentbernat, originally wrote about it on his blog.

TODO: https://medium.com/@henrebotha/how-to-write-an-asynchronous-zsh-prompt-b53e81720d32

[[ $USERNAME != "root" ]] && [[ $ZSH_NAME != "zsh-static" ]] && {

    # Async helpers
    _vbe_vcs_async_start() {
        async_start_worker vcs_info
        async_register_callback vcs_info _vbe_vcs_info_done
    }
    _vbe_vcs_info() {
        cd -q $1
        vcs_info
        print ${vcs_info_msg_0_}
    }
    _vbe_vcs_info_done() {
        local job=$1
        local return_code=$2
        local stdout=$3
        local more=$6
        if [[ $job == '[async]' ]]; then
            if [[ $return_code -eq 2 ]]; then
                # Need to restart the worker. Stolen from
                # https://github.com/mengelbrecht/slimline/blob/master/lib/async.zsh
                _vbe_vcs_async_start
                return
            fi
        fi
        vcs_info_msg_0_=$stdout
        [[ $more == 1 ]] || zle reset-prompt
    }

    autoload -Uz vcs_info

    zstyle ':vcs_info:*' enable git
    () {
        local formats="${PRCH[branch]} %b%c%u"
        local actionformats="${formats}%{${fg[default]}%} ${PRCH[sep]} %{${fg[green]}%}%a"
        zstyle    ':vcs_info:*:*' formats           $formats
        zstyle    ':vcs_info:*:*' actionformats     $actionformats
        zstyle    ':vcs_info:*:*' stagedstr         "%{${fg[green]}%}${PRCH[circle]}"
        zstyle    ':vcs_info:*:*' unstagedstr       "%{${fg[yellow]}%}${PRCH[circle]}"
        zstyle    ':vcs_info:*:*' check-for-changes true

        zstyle ':vcs_info:git*+set-message:*' hooks git-untracked

        +vi-git-untracked(){
            if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' ]] && \
                git status --porcelain 2> /dev/null | grep -q '??' ; then
                hook_com[staged]+="%{${fg[black]}%}${PRCH[circle]}"
            fi
        }

    }

    # Asynchronous VCS status
    async_init
    _vbe_vcs_async_start
    add-zsh-hook precmd (){
        async_job vcs_info _vbe_vcs_info $PWD
    }
    add-zsh-hook chpwd (){
        vcs_info_msg_0_=
    }

    # Add VCS information to the prompt
    _vbe_add_prompt_vcs () {
        _vbe_prompt_segment cyan default ${vcs_info_msg_0_}
    }
}

Bindings

Jump words

I’d like to have a word boundary at the equal = sign, so remove it from $WORDCHARS.

#WORDCHARS='*?_-.[]~=/&;!#$%^(){}<>'
WORDCHARS='*?_-.[]~/&;!#$%^(){}<>'

Define keys

From: https://wiki.archlinux.org/index.php/Zsh#Key_bindings

Create a zkbd compatible hash. To add other keys to this hash, see man 5 terminfo.

typeset -g -A key

key[Home]="${terminfo[khome]}"
key[End]="${terminfo[kend]}"
key[Insert]="${terminfo[kich1]}"
key[Backspace]="${terminfo[kbs]}"
key[Delete]="${terminfo[kdch1]}"
key[Up]="${terminfo[kcuu1]}"
key[Down]="${terminfo[kcud1]}"
key[Left]="${terminfo[kcub1]}"
key[Right]="${terminfo[kcuf1]}"
key[PageUp]="${terminfo[kpp]}"
key[PageDown]="${terminfo[knp]}"
key[Shift-Tab]="${terminfo[kcbt]}"

Set up key accordingly.

[[ -n "${key[Home]}"      ]] && bindkey -- "${key[Home]}"      beginning-of-line
[[ -n "${key[End]}"       ]] && bindkey -- "${key[End]}"       end-of-line
[[ -n "${key[Insert]}"    ]] && bindkey -- "${key[Insert]}"    overwrite-mode
[[ -n "${key[Backspace]}" ]] && bindkey -- "${key[Backspace]}" backward-delete-char
[[ -n "${key[Delete]}"    ]] && bindkey -- "${key[Delete]}"    delete-char
[[ -n "${key[Up]}"        ]] && bindkey -- "${key[Up]}"        up-line-or-history
[[ -n "${key[Down]}"      ]] && bindkey -- "${key[Down]}"      down-line-or-history
[[ -n "${key[Left]}"      ]] && bindkey -- "${key[Left]}"      backward-char
[[ -n "${key[Right]}"     ]] && bindkey -- "${key[Right]}"     forward-char
[[ -n "${key[PageUp]}"    ]] && bindkey -- "${key[PageUp]}"    beginning-of-buffer-or-history
[[ -n "${key[PageDown]}"  ]] && bindkey -- "${key[PageDown]}"  end-of-buffer-or-history
[[ -n "${key[Shift-Tab]}" ]] && bindkey -- "${key[Shift-Tab]}" reverse-menu-complete

Finally, make sure the terminal is in application mode, when zle is active. Only then are the values from $terminfo valid.

if (( ${+terminfo[smkx]} && ${+terminfo[rmkx]} )); then
  autoload -Uz add-zle-hook-widget
  function zle_application_mode_start { echoti smkx }
  function zle_application_mode_stop { echoti rmkx }
  add-zle-hook-widget -Uz zle-line-init zle_application_mode_start
  add-zle-hook-widget -Uz zle-line-finish zle_application_mode_stop
fi

History

Set the zsh history file (if not already set).

if [ -z "$HISTFILE" ]; then
  HISTFILE="$XDG_DATA_HOME"/zsh/history
fi

Set the history size.

HISTSIZE=10000
SAVEHIST=10000

Create alias to print out the history with yyyy-mm-dd timestamps.

alias history="fc -il 1"

Configure the history:

append_history
Append instead of replacing history file.
extended_history
Save commands with timestamp and duration.
hist_expire_dups_first
Trim oldest history event with duplicate before unique events.
hist_ignore_dups
Ignore if command is duplicate of the previous command.
hist_ignore_space
Ignore command if the first character is a space.
hist_verify
Whenever the user enters a line with history expansion, don’t execute the line directly.
inc_append_history
Add history lines incrementally (as soon as they are entered).
share_history
Share history with your zshells on the same host.
setopt append_history
setopt extended_history
setopt hist_expire_dups_first
setopt hist_ignore_dups
setopt hist_ignore_space
setopt hist_verify
setopt inc_append_history
setopt share_history

History completion

When pressing up/down arrow keys find command in history beginning with what you’ve typed already.

See: https://superuser.com/a/418299/94259

From: https://wiki.archlinux.org/index.php/Zsh#History_search

autoload -Uz up-line-or-beginning-search down-line-or-beginning-search
zle -N up-line-or-beginning-search
zle -N down-line-or-beginning-search

[[ -n "${key[Up]}"   ]] && bindkey -- "${key[Up]}"   up-line-or-beginning-search
[[ -n "${key[Down]}" ]] && bindkey -- "${key[Down]}" down-line-or-beginning-search

Completion

Load module.

zmodload -i zsh/complist

Configure some options:

menu_complete
Unset to not insert the first match immediately.
flowcontrol
Unset to disable output flow control via start/stop characters (usually assigned to ^S/Q).
auto_menu
Use menu completion by pressing the tab key repeatedly.
complete_in_word
Do completion from both ends of the word.
always_to_end
When full completion is inserted, the cursor is moved to the end of the word.
unsetopt menu_complete
unsetopt flowcontrol
setopt auto_menu
setopt complete_in_word
setopt always_to_end

Use menu like selection mode.

zstyle ':completion:*:*:*:*:*' menu select

When pressing Shift-Tab move through the completion menu backwards.

if [[ "${terminfo[kcbt]}" != "" ]]; then
  bindkey "${terminfo[kcbt]}" reverse-menu-complete
fi

Initialize completion

autoload -U compinit compinit
compinit

Tools

ssh-agent

Start ssh-agent, or take over the environment variables if it is already running.

SSH_ENV="$HOME/.ssh/environment"

function start_agent {
    echo "Initialising new SSH agent..."
    /usr/bin/ssh-agent | sed 's/^echo/#echo/' > "${SSH_ENV}"
    echo succeeded
    chmod 600 "${SSH_ENV}"
    . "${SSH_ENV}" > /dev/null
    /usr/bin/ssh-add;
}

# Source SSH settings, if applicable
if [ -f "${SSH_ENV}" ]; then
    . "${SSH_ENV}" > /dev/null
    ps ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || {
        start_agent;
    }
else
    start_agent;
fi

Mise

Configure mise. I don’t like all the magic mise ships with, so instead of having mise activate in my zshrc, I force myself to type mise use x@y.

I also don’t want mise to use versions from .tool-versions, because I don’t care if the exact same version is installed.

#eval "$($HOME/.local/bin/mise activate zsh)"
export MISE_DEFAULT_TOOL_VERSIONS_FILENAME=.i-dont-want-to-use-any-tool-versions

Chruby

Currently I’m not using chruby and use mise instead.

Locate where the chruby scripts are.

(seq-find 'file-accessible-directory-p
          '("/usr/local/share/chruby" "/usr/share/chruby"))

Load chruby, the Ruby version changer.

source nil/chruby.sh

Also automatically use the default version.

source nil/auto.sh

Run chruby to load a Ruby.

chruby > /dev/null

Aliases

Basics

Just some simple aliases I use every day.

alias l="ls -la"

Parent dirs

Just use more dots to go further up.

alias -g ...='../..'
alias -g ....='../../..'
alias -g .....='../../../..'

Bundler

Short aliases for bundler. Inspired by Vendor Everything Still Applies.

alias b="bundle"
alias bi="b install --path vendor/bundle"
alias bil="bi --local"
alias bu="b update"
alias be="b exec"
alias binit="bi && b package && echo 'vendor/ruby' >> .gitignore"

Emacsclient

Add a function for emacsclient that allows to read from stdin.

Originally from EmacsWiki.

e() {
  if [ -z "$1" ]
  then
    local TMP;
    TMP="$(mktemp /tmp/emacs-stdin-XXX)"
    cat >$TMP
    emacsclient --alternate-editor=emacs --no-wait $TMP
  else
    emacsclient --alternate-editor=emacs --no-wait "$@"
  fi
}

magit

And an alias to directly start magit in the current working directory.

alias magit='emacsclient -n -e "(progn (magit-status) (delete-other-windows))"'

git

I use git a lot, so add some easy to use aliases.

alias gst="git status"
alias gco="git checkout"

GitLab

The GitLab documentation uses gitlab-rake to run rake tasks, so because I’m lazy I want to copy/paste the commands as-is.

alias gitlab-rake="be rake"

No littering

Some commands litter my $HOME directory. Stop them from doing that with a few aliases.

Units

alias units="units --history=$XDG_CACHE_HOME/units_history"

Functions

Smart chruby

Automatically guess the ruby version from:

  • Given argument
  • Gemfile
  • .ruby_version
  • ~/.ruby_version
function chrb () {
    if [ -n "$1" ]
    then
        chruby $@
    elif [ -e Gemfile ]
    then
        chruby $(grep ^ruby Gemfile | tr -cd '[[:digit:]].')
    elif [ -e .ruby_version ]
    then
        chruby_auto
    else
        chruby
    fi
    # print the current ruby version
    env ruby --version
}

Zprofile

Zprofile is only loaded on the login shell.

Profile

Load environment.

setopt allexport

source $HOME/.config/environment.d/*.conf

unsetopt allexport