Parcourir la source

Initial migration of shell config

Shell config now caters to both bash and zsh.
Scripts common to bash and zsh have been split into a 'common' folder.
Install scripts have been updated to install bash and zsh separately.
Comprehensive configuration of zsh and bash has yet to be done, but the
basics are in place.
Weiyi Lou il y a 9 ans
Parent
commit
1cbab22714

+ 13 - 7
Makefile

@@ -2,8 +2,8 @@
 # dotfiles setup. Run `make install` to perform all setup tasks.
 
 # Groups of targets
-all = git zsh tmux ack vimperator vim
-rm-all = rm-git rm-zsh rm-tmux rm-ack rm-vimperator rm-vim
+all = git bash zsh tmux ack vimperator vim
+rm-all = rm-git rm-bash rm-zsh rm-tmux rm-ack rm-vimperator rm-vim
 
 # Auto-Documenting Section. Displays a target list with `##` descriptions.
 help:
@@ -26,8 +26,7 @@ show-versions: ## List versions of installed software.
 # Setup Tasks {{{
 
 prep:
-	@mkdir -p ~/.ssh $(XDG_CONFIG_HOME)
-	@touch ~/.ssh/known_hosts
+	@mkdir -p $(XDG_CONFIG_HOME)
 
 XDG_CONFIG_HOME ?= $(HOME)/.config
 
@@ -45,9 +44,16 @@ rm-git:
 	git config --global --unset core.excludesFile ".*/dotfiles/git/globalignore"
 	git config --global --unset diff.mnemonicPrefix true
 
-zsh: submodules
-	@./bin/linkup ~/dotfiles/zsh/zshenv ~/.zshenv
-	@./bin/linkup ~/dotfiles/zsh/zshrc ~/.zshrc
+bash:
+	@./bin/linkup ~/dotfiles/shell/bash_profile ~/.bash_profile
+	@./bin/linkup ~/dotfiles/shell/bashrc ~/.bashrc
+rm-bash:
+	rm ~/.bash_profile
+	rm ~/.bashrc
+
+zsh:
+	@./bin/linkup ~/dotfiles/shell/env ~/.zshenv
+	@./bin/linkup ~/dotfiles/shell/zshrc ~/.zshrc
 rm-zsh:
 	rm ~/.zshenv
 	rm ~/.zshrc

+ 16 - 11
README.md

@@ -2,25 +2,25 @@
 
 Configuration files for:
 
+- Bash (3.2+) and Zsh (4.3.17+)
 - Vim (7.4+) or NeoVim
 - Vimperator (3.8+)
 - Tmux (1.8+)
-- Zsh (4.3.17+)
 - Other bits and pieces
 
 Heavy preference for `vim`-like bindings.
 
 ## Installation
 
-Clone to a home folder.
-Run `make install` or `./install`.
+Clone to a home folder and run `make install` or `./install`.
+
 Restart the terminal session.
 
 ## Recommended
 
 - Colour palette: [Solarized][].
 - Font: [Meslo for Powerline][] (works well with [Rainbarf][]).
-- NeoVim or Vim+Python/Ruby support - for [Vim-Plug][] parallel downloads.
+- [Vim-Plug][] parallel downloads - requires Vim+Python/Ruby support, or Neovim.
 - [The Silver Searcher][] - fast code search ([Ack][] included as fallback).
 - [Pandoc][] - Vim creates documents from `.pandoc` files ([Pandoc Markdown][]).
 - [pandoc-citeproc][] - bibliographical assistance when using Pandoc.
@@ -39,14 +39,19 @@ Restart the terminal session.
 
 Below is a non-exhaustive list of dotfiles usage.
 
-### Zsh
+### Bash/Zsh
+
+- Case-insensitive completion.
+- The excellent [z][] and [v][] commands:
+  - `z` for folder jumping: `z regex` = `cd /path/with/regex`.
+  - `v` for file editing: `v regex` = `vim /path/with/regex`.
+- Multi-line shell prompt that displays Git repo info and job count.
+- Mostly-Mnemonic Git shortcuts: `gs` = `git status`, `gd` = `git diff`, etc.
+- SSH agent automated - attempts to load identities with `ssh-add` on start.
+- Machine-specific settings can be contained in `.bashlocal` and `.zshlocal`.
 
-- `z` folder jumping enabled, i.e. `z regex` = `cd /path/with/regex`.
-- Shell prompt displays Git repository info and work tree status.
-- Mostly-Mnemonic Git shortcuts, e.g. `gs` is `git status`, `gd` is `git diff`.
-- SSH agent use is encouraged. `ssh-add` is run at shell start if an identity is
-  not already loaded.
-- A `.zshlocal` file can contain machine-specific settings.
+[z]: https://github.com/rupa/z
+[v]: https://github.com/rupa/v
 
 ### Vim
 

+ 13 - 0
bin/git-recover-lost-index

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+
+# Recover indexed/staged changes that were lost.
+#
+# Though not commited yet, git actually stores information about staged changes.
+# This function locates all dangling/orphaned blobs and puts them in text files.
+# These files can then be checked for changes that a user has lost.
+# http://blog.ctp.com/2013/11/21/git-recovering-from-mistakes/
+for blob in $(git fsck --lost-found | awk '$2 == "blob" { print $3 }'); do
+  git cat-file -p $blob > $blob.txt;
+done

+ 1 - 0
bin/show-versions

@@ -6,6 +6,7 @@ IFS=$'\n\t'
 
 echo "Installed:"
 command -v git >/dev/null && git version || true
+command -v bash >/dev/null && bash --version | head -n 1 || true
 command -v zsh >/dev/null && zsh --version || true
 command -v tmux >/dev/null && tmux -V || true
 command -v vim >/dev/null  && vim --version | head -n 1 || true

+ 54 - 0
bin/v

@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+[ "$vim" ] || vim=vim
+[ $viminfo ] || viminfo=~/.viminfo
+
+usage="$(basename $0) [-a] [-l] [-[0-9]] [--debug] [--help] [regexes]"
+
+[ $1 ] || list=1
+
+fnd=()
+for x; do case $x in
+    -a) deleted=1;;
+    -l) list=1;;
+    -[0-9]) edit=${x:1}; shift;;
+    --help) echo $usage; exit;;
+    --debug) vim=echo;;
+    --) shift; fnd+=("$@"); break;;
+    *) fnd+=("$x");;
+esac; shift; done
+set -- "${fnd[@]}"
+
+[ -f "$1" ] && {
+    $vim "$1"
+    exit
+}
+
+while IFS=" " read line; do
+    [ "${line:0:1}" = ">" ] || continue
+    fl=${line:2}
+    [ -f "${fl/\~/$HOME/}" -o "$deleted" ] || continue
+    match=1
+    for x; do
+        [[ "$fl" =~ $x ]] || match=
+    done
+    [ "$match" ] || continue
+    i=$((i+1))
+    files[$i]="$fl"
+done < "$viminfo"
+
+if [ "$edit" ]; then
+    resp=${files[$((edit+1))]}
+elif [ "$i" = 1 -o "$list" = "" ]; then
+    resp=${files[1]}
+elif [ "$i" ]; then 
+    while [ $i -gt 0 ]; do
+         echo -e "$((i-1))\t${files[$i]}"
+         i=$((i-1))
+    done
+    read -p '> ' CHOICE
+    [ "$CHOICE" ] && resp=${files[$((CHOICE+1))]}
+fi
+
+[ "$resp" ] || exit
+$vim "${resp/\~/$HOME}"

+ 8 - 5
install

@@ -6,9 +6,8 @@ IFS=$'\n\t'
 # dotfiles installer for when `make` is not available.
 
 # Prep tasks
-mkdir -p ~/.ssh ${XDG_CONFIG_HOME:=$HOME/.config}
-touch ~/.ssh/known_hosts
-echo "Preparing."
+mkdir -p ${XDG_CONFIG_HOME:=$HOME/.config}
+echo "Prepared."
 
 # Initialise submodules
 (cd ~/dotfiles && git submodule sync && git submodule update --init)
@@ -20,9 +19,13 @@ git config --global core.excludesfile ~/dotfiles/git/globalignore
 git config --global diff.mnemonicPrefix true
 echo "Git config done."
 
+# Bash
+~/dotfiles/bin/linkup ~/dotfiles/shell/bash_profile ~/.bash_profile
+~/dotfiles/bin/linkup ~/dotfiles/shell/bashrc ~/.bashrc
+
 # Zsh
-~/dotfiles/bin/linkup ~/dotfiles/zsh/zshenv ~/.zshenv
-~/dotfiles/bin/linkup ~/dotfiles/zsh/zshrc ~/.zshrc
+~/dotfiles/bin/linkup ~/dotfiles/shell/env ~/.zshenv
+~/dotfiles/bin/linkup ~/dotfiles/shell/zshrc ~/.zshrc
 
 # Tmux
 ~/dotfiles/bin/linkup ~/dotfiles/tmux/tmux.conf ~/.tmux.conf

+ 2 - 0
shell/bash/completion.bash

@@ -0,0 +1,2 @@
+bind "set completion-ignore-case on"
+bind "set show-all-if-ambiguous on"

+ 24 - 0
shell/bash/tmuxinator.bash

@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+_tmuxinator() {
+    COMPREPLY=()
+    local word
+    word="${COMP_WORDS[COMP_CWORD]}"
+
+    if [ "$COMP_CWORD" -eq 1 ]; then
+        local commands="$(compgen -W "$(tmuxinator commands)" -- "$word")"
+        local projects="$(compgen -W "$(tmuxinator completions start)" -- "$word")"
+ 
+        COMPREPLY=( $commands $projects )
+    elif [ "$COMP_CWORD" -eq 2 ]; then
+        local words
+        words=("${COMP_WORDS[@]}")
+        unset words[0]
+        unset words[$COMP_CWORD]
+        local completions
+        completions=$(tmuxinator completions "${words[@]}")
+        COMPREPLY=( $(compgen -W "$completions" -- "$word") )
+    fi
+}
+
+complete -F _tmuxinator tmuxinator mux

+ 1 - 0
shell/bash_profile

@@ -0,0 +1 @@
+source ~/.bashrc

+ 37 - 0
shell/bashrc

@@ -0,0 +1,37 @@
+source ~/dotfiles/shell/env
+
+[[ -r ~/.bashlocal ]] && source ~/.bashlocal
+
+# Use vi-bindings in readline for command editing
+set -o vi
+
+source ~/dotfiles/shell/common/load
+
+for file in ~/dotfiles/shell/bash/*.bash; do
+  source $file
+done
+
+# Command Prompt
+#
+#     [host]  directory  gitinfo  jobinfo
+#     user -
+#
+PROMPT_COMMAND='PS1="
+${FG[6]}[\h]  ${FG[3]}$(shortcwd)$(__gitp "  %s")$(getajob)
+${FG[5]}\u - $cReset"'
+
+# Display suspended/backgrounded job count, if any.
+function getajob() {
+  local jobcount=$(jobs | wc -l)
+  [[ $jobcount -ne 0 ]] && printf "  ${FG[63]}[jobs]: ${FG[1]}\j" || printf ''
+}
+
+# Display up to 3 segments of the current working directory.
+function shortcwd() {
+  local folder=$(pwd) fld='[^/]*'
+  folder=${folder/$HOME/"~"}
+  folder=$(echo $folder | sed 's|.*/\('$fld'/'$fld'/'$fld'\)|\1|')
+  printf $folder
+}
+
+[ -f ~/.fzf.bash ] && source ~/.fzf.bash

+ 18 - 0
shell/common/auto-ssh-hosts.sh

@@ -0,0 +1,18 @@
+# Automagic ssh to known hosts.
+# Configure usernames for hosts in .ssh/config
+autosshhosts() {
+  local line host
+  IFS=$'\n\t'
+  for line in $(grep "^[a-zA-Z]" ~/.ssh/known_hosts); do
+    host=${line%%[, ]*}
+    alias $host="ssh $host"
+  done
+  unset IFS
+}
+
+if [[ -f ~/.ssh/known_hosts ]]; then
+  autosshhosts
+fi
+
+# Hosts file
+alias hosts='sudo vim /etc/hosts'

+ 33 - 0
shell/common/coloured-man-pages.sh

@@ -0,0 +1,33 @@
+# From oh-my-zsh
+if [[ "$OSTYPE" = solaris* ]]
+then
+  if [[ ! -x "$HOME/bin/nroff" ]]
+  then
+    mkdir -p "$HOME/bin"
+    cat > "$HOME/bin/nroff" <<EOF
+#!/bin/sh
+if [ -n "\$_NROFF_U" -a "\$1,\$2,\$3" = "-u0,-Tlp,-man" ]; then
+  shift
+  exec /usr/bin/nroff -u\$_NROFF_U "\$@"
+fi
+#-- Some other invocation of nroff
+exec /usr/bin/nroff "\$@"
+EOF
+    chmod +x "$HOME/bin/nroff"
+  fi
+fi
+
+man() {
+  env \
+    LESS_TERMCAP_mb=$(printf "\e[1;31m") \
+    LESS_TERMCAP_md=$(printf "\e[1;31m") \
+    LESS_TERMCAP_me=$(printf "\e[0m") \
+    LESS_TERMCAP_se=$(printf "\e[0m") \
+    LESS_TERMCAP_so=$(printf "\e[1;44;33m") \
+    LESS_TERMCAP_ue=$(printf "\e[0m") \
+    LESS_TERMCAP_us=$(printf "\e[1;32m") \
+    PAGER="${commands[less]:-$PAGER}" \
+    _NROFF_U=1 \
+    PATH="$HOME/bin:$PATH" \
+      man "$@"
+}

+ 61 - 0
shell/common/colours

@@ -0,0 +1,61 @@
+# vim: ft=sh
+# A script to make using 256 colours less painful.
+#
+# Adapted from http://github.com/sykora/etc/blob/master/zsh/functions/spectrum/
+# P.C. Shyamshankar <sykora@lucentbeing.com>
+
+# Create foreground and background colour arrays for 256 colours.
+declare -a FG BG
+
+# Determine prompt colour escape strings.
+if [[ -n ${BASH_VERSION-} ]]; then
+  cStart='\[\e['
+  cEnd='m\]'
+elif [[ -n ${ZSH_VERSION-} ]]; then
+  # ZSH specifics: A = associative array, H = hide value when echoed, g = global
+  declare -AHg FG BG
+  cStart='%{['
+  cEnd='m%}'
+fi
+
+# The arrays will be 1-based because this is ZSH default behaviour.
+# This can be changed with the `KSH_ARRAYS` option, but we do not.
+# First 16 are the ANSI Terminal colours and bright variants:
+# Black, Red, Green, Yellow, Blue, Magenta, Cyan, White
+for colour in {0..255}; do
+    FG[$colour]=$cStart"38;5;"$colour$cEnd
+    BG[$colour]=$cStart"48;5;"$colour$cEnd
+done
+
+# Create text effects.
+cReset=$cStart"0"$cEnd
+cBol=$cStart"1"$cEnd # bold
+cIta=$cStart"3"$cEnd # italic
+cUnd=$cStart"4"$cEnd # underline
+cBli=$cStart"5"$cEnd # blink
+cRev=$cStart"7"$cEnd # reverse
+cnBol=$cStart"22"$cEnd # no-bold
+cnIta=$cStart"23"$cEnd # no-italic
+cnUnd=$cStart"24"$cEnd # no-underline
+cnBli=$cStart"25"$cEnd # no-blink
+cnRev=$cStart"27"$cEnd # no-reverse
+
+SPECTRUM_TEXT=${SPECTRUM_TEXT:-Arma virumque cano Troiae qui primus ab oris}
+
+# Show all 256 colours with colour number
+function spectrum_ls() {
+  local line
+  for code in {0..255}; do
+    line="$code: ${FG[$code]}$SPECTRUM_TEXT$cReset"
+    [[ -n $(command -v print) ]] && print -P -- $line || printf "$line\n"
+  done
+}
+
+# Show all 256 colours where the background is set to specific colour
+function spectrum_bls() {
+  local line
+  for code in {0..255}; do
+    line="$code: ${BG[$code]}$SPECTRUM_TEXT$cReset"
+    [[ -n $(command -v print) ]] && print -P -- $line || printf "$line\n"
+  done
+}

+ 7 - 0
shell/common/directories.sh

@@ -0,0 +1,7 @@
+# Folder listing/traversal
+alias l='ls'
+alias la='ls -A'
+alias lal='ls -Alh'
+
+# Common places
+alias cd.='cd ~/dotfiles'

+ 14 - 0
shell/common/fzf.sh

@@ -0,0 +1,14 @@
+# Fuzzy Finder in Go/Ruby
+
+if [[ -x $(command -v ag 2> /dev/null) ]]; then
+  # Setting ag as the default source for fzf
+  export FZF_DEFAULT_COMMAND='ag -l -g ""'
+else
+  # Otherwise at least use `git ls-tree` if available.
+  export FZF_DEFAULT_COMMAND='
+  (git ls-tree -r --name-only HEAD ||
+    find * -name ".*" -prune -o -type f -print -o -type l -print) 2> /dev/null'
+fi
+
+# To apply the command to CTRL-T as well (paste selection into command line)
+export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

+ 241 - 0
shell/common/git-prompt.sh

@@ -0,0 +1,241 @@
+# Display git repo information in a shell prompt
+#
+# Format:
+#
+#     [repo:branch:commit] BARE BISECT/MERGE/REBASE NO-REMOTE/NO-RTB/AHEAD/BEHIND/DIVERGED WIP STASH +±xcru?
+#
+# Usage:
+#
+# Call `__gitp` in such a way that prompt expansion happens i.e. from bash's
+# `PROMPT_COMMAND` or zsh's `PROMPT`/`pre_cmd()`.
+#
+function __gitp() {
+  # Preserve the exit status of the previous command.
+  local exit=$?
+
+  # Only run if git exists.
+  [[ -z $(command -v git) ]] && return $exit
+
+  # Try to get git info. Also serves to check if current folder is a git repo.
+  local gitinfo ret
+  gitinfo=$(git rev-parse \
+    --git-dir \
+    --is-bare-repository \
+    --is-inside-work-tree \
+    --short HEAD 2>/dev/null)
+  ret="$?"
+  [[ -z $gitinfo ]] && return $exit
+
+  # Explode git info into individual variables.
+  local gitdir=${gitinfo%%$'\n'*}; gitinfo=${gitinfo#*$'\n'}
+  local bare=${gitinfo%%$'\n'*}; gitinfo=${gitinfo#*$'\n'}
+  local worktree=${gitinfo%%$'\n'*}; gitinfo=${gitinfo#*$'\n'}
+  local commit; [[ $ret = "0" ]] && commit=$gitinfo
+
+  __gitp_cprep
+
+  # Prepare display elements.
+  [[ $bare = 'true' ]] && bare=${GITP_BARE:-"$cRed BARE"} || bare=''
+  local location="$(__gitp_location $commit)"
+  if [[ $worktree = 'true' ]]; then
+    # Many things only matter if in a work tree (i.e. not bare, not in gitdir).
+    __gitp_status
+    local op="$(__gitp_op $gitdir)"
+    local remote="$(__gitp_remote)"
+    local wips="$(__gitp_wips $gitdir)"
+    local changes="$(__gitp_changes)"
+  fi
+  # Colour the location if there are local changes.
+  local dirty=$(__gitp_dirty)
+
+  # Display the git prompt.
+  local formatting=${1:-' %s'}
+  printf -- "$formatting" "$dirty$location$bare$op$remote$wips$changes"
+  return $exit
+}
+
+# Get git status output for parsing.
+function __gitp_status() {
+  GITP_STATUS=''
+
+  # Ignore work tree changes in submodules to speed up prompt rendering.
+  GITP_STATUS=$(git status --porcelain --ignore-submodules=dirty 2>/dev/null) \
+    || GITP_STATUS=$(git status --porcelain)
+}
+
+# Initialise colour strings.
+function __gitp_cprep() {
+  if [[ -n ${BASH_VERSION-} ]]; then
+    cStart=${cStart:-'\[\e['}
+    cEnd=${cEnd:-'m\]'}
+  elif [[ -n ${ZSH_VERSION-} ]]; then
+    cStart=${cStart:-'%{['}
+    cEnd=${cEnd:-'m%}'}
+  fi
+  cReset=${cReset:-$cStart'0'$cEnd}
+  cRed=$cStart'38;5;1'$cEnd
+  cGre=$cStart'38;5;2'$cEnd
+  cBlu=$cStart'38;5;4'$cEnd
+  cMag=$cStart'38;5;5'$cEnd
+  cgGre=$cStart'38;5;70'$cEnd
+  cgRed=$cStart'38;5;124'$cEnd
+  cgYel=$cStart'38;5;220'$cEnd
+}
+
+# Get location i.e. which repo/branch/commit is this?
+function __gitp_location() {
+  local prefix=${GITP_PREFIX:-'['}
+  local suffix=${GITP_SUFFIX:-"]"}
+  local repo=$(__gitp_repo)
+  local branch=$(__gitp_branch)
+  [[ -z $branch ]] && branch=$(git describe --tags --exact-match 2>/dev/null)
+  echo "$prefix$repo:$branch:$1$suffix"
+}
+
+# Determine if a repo is in the middle of an "operation" e.g. rebase, bisect.
+function __gitp_op() {
+  local bisect=${GITP_BISECT:-"$cRed BISECT"}
+  local  merge=${GITP_MERGE:-"$cRed MERGE"}
+  local rebase=${GITP_REBASE:-"$cRed REBASE"}
+  local op
+
+  if [[ -e "$1/BISECT_LOG" ]]; then
+    op+="$bisect"
+  elif [[ -e "$1/MERGE_HEAD" ]]; then
+    op+="$merge"
+  elif [[ -e "$1/rebase" || -e "$1/rebase-apply" ||  -e "$1/rebase-merge" || -e "$1/../.dotest" ]]; then
+    op+="$rebase"
+  fi
+  echo "$op"
+}
+
+# Compare the local and remote tracking branches (upstream).
+function __gitp_remote() {
+  local noremote=${GITP_NOREMOTE:-"$cMag NO-REMOTE"}
+  local    nortb=${GITP_NORTB:-"$cMag NO-RTB"}
+  local    ahead=${GITP_AHEAD:-"$cMag AHEAD"}
+  local   behind=${GITP_BEHIND:-"$cMag BEHIND"}
+  local diverged=${GITP_DIVERGED:-"$cRed DIVERGED"}
+  local remote commits count ahd=0 bhd=0
+
+  # Count commits ahead and behind.
+  if commits="$(git rev-list --left-right @{upstream}...HEAD 2>/dev/null)"; then
+    [[ -n ${ZSH_VERSION-} ]] && commits=(${(f)commits})
+    for commit in $commits; do
+      case "$commit" in
+        "<"*) ((bhd++)) ;;
+        *)    ((ahd++)) ;;
+      esac
+    done
+    count="$bhd  $ahd"
+  fi
+
+  # Determine what to display.
+  case "$count" in
+    "") # no upstream
+    if [[ -z "$(git remote -v)" ]]; then
+      remote="$noremote"
+    elif [[ -n "$(__gitp_branch)" ]]; then
+      remote="$nortb"
+    fi
+    ;;
+    "0  0") ;;                       # Equal to upstream.
+    "0  "*) remote="$ahead-$ahd" ;;  # Ahead of upstream.
+    *"  0") remote="$behind-$bhd" ;; # Behind upstream.
+    *)      remote="$diverged" ;;    # Diverged from upstream.
+  esac
+
+  echo "$remote"
+}
+
+# Warn if the current commit is a work-in-progress or if there are stashed
+# changes.
+#
+# WIP commits are made with `gwip` and removed with `gunwip`.
+function __gitp_wips() {
+  local wip=${GITP_WIP:-"$cRed WIP"}
+  local stash=${GITP_STASH:-"$cRed STASH"}
+  local wips
+
+  if $(git log -n 1 2>/dev/null | grep -q -c "\-\-wip\-\-"); then
+    wips="$wip"
+  fi
+
+  [[ -e "$1/refs/stash" ]] && wips+="$stash"
+
+  echo "$wips"
+}
+
+# Work tree change indicators
+#
+# Prints symbol for each change in `git status`, up to the number of symbols
+# indicated by `GITP_CHANGES_MAX`. Gives a good visual of what has changed.
+#
+# Performance note: if the git prompt is slow, it is more because `git status`
+# is slow. Change-string building is near-instantaneous since it is just string
+# manipulation.
+function __gitp_changes() {
+  [[ -z $GITP_STATUS ]] && return
+
+  local changes_max=${GITP_CHANGES_MAX:-20}
+  local       i_col=${GITP_COLOUR_INDEX:-$cgGre}
+  local       w_col=${GITP_COLOUR_WTREE:-$cgRed}
+  local       u_col=${GITP_COLOUR_UN:-$cgYel}
+  local       e_col=${GITP_COLOUR_END:-$cReset}
+  local lines line X Y x_set y_set u_set end count=0
+
+  # Split git status by newline into array elements.
+  if [[ -n ${BASH_VERSION-} ]]; then
+    IFS=$'\n'
+    lines=$GITP_STATUS
+  elif [[ -n ${ZSH_VERSION-} ]]; then
+    lines=(${(f)GITP_STATUS})
+  fi
+
+  for line in $lines; do
+    (( count+=1 ))
+    (( count >= $changes_max )) && end='..' && break
+    X=${line:0:1}
+    Y=${line:1:1}
+    [[ $X$Y = '??' ]] && u_set+="?" && continue
+    [[ $X = 'U' ]] || [[ $Y = 'U' ]] && u_set+="u" && continue
+    [[ $X$Y = 'DD' ]] || [[ $X$Y = 'AA' ]] && u_set+="u" && continue
+    [[ $Y = 'M' ]] && y_set+="+"
+    [[ $Y = 'D' ]] && y_set+="x"
+    [[ $X = 'M' ]] && x_set+="+" && continue
+    [[ $X = 'A' ]] && x_set+="±" && continue
+    [[ $X = 'D' ]] && x_set+="x" && continue
+    [[ $X = 'R' ]] && x_set+="r" && continue
+    [[ $X = 'C' ]] && x_set+="c" && continue
+  done
+
+  [[ -n ${BASH_VERSION-} ]] && unset IFS
+
+  echo " $i_col$x_set$w_col$y_set$u_col$u_set$e_col$end$cReset"
+}
+
+# Check if a repo is modified
+function __gitp_dirty() {
+  local clean=${GITP_CLEAN:-$cReset$cGre}
+  local dirty=${GITP_DIRTY:-$cReset$cBlu}
+  [[ -z $GITP_STATUS ]] && echo "$clean" || echo "$dirty"
+}
+
+# Get repository name
+#
+# Check for a name in git remotes, between ':' or '/' and a space.
+function __gitp_repo() {
+  local repo="$(git remote -v | head -n 1 2> /dev/null)"
+  repo=${repo##*[:|/]}
+  repo=${repo% *}
+  printf "${repo%.git}"
+}
+
+# Get current branch name
+#
+# Prints the branch name or tag of the current commit, if any.
+function __gitp_branch() {
+  local ref
+  ref=$(git symbolic-ref --quiet HEAD 2>/dev/null)
+  printf "${ref#refs/heads/}"
+}

+ 62 - 0
shell/common/git.sh

@@ -0,0 +1,62 @@
+# Git Aliases (mostly mnemonic)
+
+alias ga.='git add -A .'
+alias ga='git add'
+alias gap='git add -p'
+alias gb='git branch'
+alias gba='git branch -a'
+alias gbav='git branch -av'
+alias gbd='git branch -D'
+alias gbv='git branch -v'
+alias gc='git commit -v'
+alias gco='git checkout'
+alias gcob='git checkout -b'
+alias gcop='git checkout -p'
+alias gd='git diff'
+alias gds='git diff --staged'
+alias gf='git fetch --all --tags && git fetch --all --prune'
+alias ggpush='git push -u origin $(__gitp_branch)'
+alias gm='git merge'
+alias gr='git reset'
+alias grh='git reset --hard'
+alias grhh='git reset HEAD --hard'
+alias grs='git reset --soft'
+alias grv='git remote -v'
+alias gs='git status'
+alias gsr='git show --format=raw' # all info about a commmit.
+
+# Git log (`gl`). Displays graph, decorations, users and dates.
+[[ $(version-compare $GIT_VER "1.8.3") -ge 0 ]] && DECO_COLOUR='%C(auto)' || DECO_COLOUR='%Cgreen'
+GL_PRETTY="'format:%C(yellow)%h %Creset%ad %Cblue%an:$DECO_COLOUR%d %Creset%s'"
+GL_OPTS="--graph --date=short --pretty=$GL_PRETTY"
+alias gl="git log $GL_OPTS"
+alias gla='gl --all' # show all refs, not only those reachable from current branch.
+alias glb='gl --simplify-by-decoration' # show mostly branches and tags.
+alias glh="git --no-pager log --max-count=15 $GL_OPTS" # show first few (head)
+alias glp='git log --graph --decorate -p'
+alias gls='git log --graph --decorate --stat'
+
+# Searching history.
+alias glG='git log --stat -G' # Search DIFFS - changes with given text.
+alias glS='git log --stat -S' # Search DIFFS - changes in number of given text.
+alias glg='git log --grep' # Search MESSAGES.
+
+# Submodule management.
+alias gsm='git submodule'
+alias gsmpull='git submodule foreach git pull origin master'
+alias gsmup='git submodule sync && git submodule update --init --recursive'
+
+# Fix tracking for origin if not there.
+alias track='git branch --set-upstream-to origin/$(__gitp_branch) && git fetch'
+
+# Autosquashing for simple fixups.
+alias grb='git rebase'
+alias grba='git rebase --abort'
+alias grbc='git rebase --continue'
+alias grbi='git rebase -i --autosquash'
+alias gcf='git commit --fixup'
+alias gcs='git commit --squash'
+
+# Create Work-In-Progress commits.
+alias gunwip='git log -n 1 | grep -q -c "\-\-wip\-\-" && git reset HEAD~1'
+alias gwip='git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit -m "--wip--"'

+ 8 - 0
shell/common/load

@@ -0,0 +1,8 @@
+# vim: ft=sh
+# Define the loading order of common shell scripts
+source ~/dotfiles/shell/common/vars
+source ~/dotfiles/shell/common/colours
+
+for file in ~/dotfiles/shell/common/*.sh; do
+  source $file
+done

+ 11 - 0
shell/common/proxy.sh

@@ -0,0 +1,11 @@
+# Set proxy environment variables
+function setproxy() {
+  proxy_address=${1:-} # default value of nothing
+  export HTTP_PROXY=$proxy_address
+  export HTTPS_PROXY=$proxy_address
+  export FTP_PROXY=$proxy_address
+  export http_proxy=$proxy_address
+  export https_proxy=$proxy_address
+  export ftp_proxy=$proxy_address
+  echo "Proxy envvars set to '$proxy_address'"
+}

+ 115 - 0
shell/common/ssh-add.sh

@@ -0,0 +1,115 @@
+# Standardize location of the ssh-agent authentication socket.
+# Perform automatic ssh-agent starting and key-loading as necessary.
+#
+# The `SSH_AUTH_SOCK` variable allows users interact with ssh-agent loaded
+# identities. Having this socket linked to a predictable place means a given
+# user can expect loaded identities to persist:
+#  - in tmux, screen and similar multiplexers
+#  - through `sudo su`/`su` to root (BUT NOT with '-' which clears envvars)
+
+# The `ID_FILES` variable contains the standard identity files that ssh-add
+# tries to load. The value can be overridden by simply declaring an alternate
+# array before this script is run i.e. near the top of bashrc/zshrc.
+ID_FILES=(${ID_FILES[@]:-~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa ~/.ssh/identity})
+
+function agent_setup() {
+  # Try to reach an existing socket.
+  linksocket
+
+  # If that does not work, try to reach the socket of the sudo user.
+  if ! agent_available; then
+    linksocket_sudo
+  fi
+
+  # If nothing worked, a no valid socket is found. If there are any identities
+  # available, create a new agent.
+  if ! agent_available && ids_available; then
+    start_sshagent
+    linksocket
+  fi
+
+  # If the agent is empty, add identities with the default file names.
+  if ! ids_loaded && ids_available; then
+    ssh-add $ID_FILES 2> /dev/null
+  fi
+
+  # The End
+}
+
+# Start a new ssh-agent process.
+function start_sshagent() {
+  echo "Starting a new SSH Agent."
+  eval `ssh-agent` &> /dev/null
+}
+
+# Link up an existing socket to the expected location if it is not already.
+#
+# Also, if the socket in the environment is a symlink, try to get to the actual
+# socket itself.
+function linksocket() {
+  local link="/tmp/ssh-agent-$USER-tmux"
+  if [[ ! -z $SSH_AUTH_SOCK && $SSH_AUTH_SOCK != $link ]]; then
+    local target=$SSH_AUTH_SOCK
+    [[ -L $SSH_AUTH_SOCK ]] && target=`readlink $SSH_AUTH_SOCK`
+    [[ -e $target ]] && ln -sf $target $link &> /dev/null
+  fi
+  export SSH_AUTH_SOCK=$link
+}
+
+# If we detect sudo, try to link the current user (usually root) to the sudo
+# user's socket directly
+#
+# A user cannot access another user's socket using a symlink, even if that user
+# has super privileges. They can, however reach that socket directly, so a
+# personal symlink is attempted.
+function linksocket_sudo() {
+  local linksudo="/tmp/ssh-agent-$SUDO_USER-tmux"
+  if [[ ! -z $SUDO_USER && -L $linksudo ]]; then
+    local link="/tmp/ssh-agent-$USER-tmux"
+    ln -sf `readlink $linksudo` $link &> /dev/null
+    export SSH_AUTH_SOCK=$link
+  fi
+}
+
+# Check if there is a usable ssh-agent.
+#
+# `ssh-add`'s return value `2` means it was unable to contact an agent.
+# Return values:
+#   0 = success, agent contacted.
+#   1 = failed to connect to an agent.
+function agent_available() {
+  ssh-add -l &> /dev/null
+  [[ $? -eq 2 ]] && return 1 || return 0
+}
+
+# Check if there are identities to load.
+#
+# Return values:
+#   0 = success, found a file.
+#   1 = failed to match any files.
+function ids_available() {
+  for file in $ID_FILES; do
+    [[ -f $file ]] && return 0
+  done
+  return 1
+}
+
+# Check if there are any identities already loaded
+#
+# Return values:
+#   0 = success, there are ids loaded into the current agent.
+#   1 = failed, there are none.
+function ids_loaded() {
+  [[ `ssh-add -L 2> /dev/null | grep "^ssh-" | wc -l` -eq 0 ]] && return 1 || return 0
+}
+
+# Go!
+agent_setup
+
+# Clean up scripts which should not be used after setup. Leave `start_sshagent`.
+unset -f agent_setup
+unset -f linksocket
+unset -f linksocket_sudo
+unset -f agent_available
+unset -f ids_available
+unset -f ids_loaded

+ 8 - 0
shell/common/vars

@@ -0,0 +1,8 @@
+# vim: ft=sh
+# Variables that other common shell scripts rely on.
+
+TERM="xterm-256color"
+
+# Git version string
+GIT_VER=$(git --version 2> /dev/null)
+GIT_VER=${GIT_VER:12}

+ 245 - 0
shell/common/z.sh

@@ -0,0 +1,245 @@
+# Copyright (c) 2009 rupa deadwyler under the WTFPL license
+
+# maintains a jump-list of the directories you actually use
+#
+# INSTALL:
+#     * put something like this in your .bashrc/.zshrc:
+#         . /path/to/z.sh
+#     * cd around for a while to build up the db
+#     * PROFIT!!
+#     * optionally:
+#         set $_Z_CMD in .bashrc/.zshrc to change the command (default z).
+#         set $_Z_DATA in .bashrc/.zshrc to change the datafile (default ~/.z).
+#         set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution.
+#         set $_Z_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself.
+#         set $_Z_EXCLUDE_DIRS to an array of directories to exclude.
+#         set $_Z_OWNER to your username if you want use z while sudo with $HOME kept
+#
+# USE:
+#     * z foo     # cd to most frecent dir matching foo
+#     * z foo bar # cd to most frecent dir matching foo and bar
+#     * z -r foo  # cd to highest ranked dir matching foo
+#     * z -t foo  # cd to most recently accessed dir matching foo
+#     * z -l foo  # list matches instead of cd
+#     * z -c foo  # restrict matches to subdirs of $PWD
+
+[ -d "${_Z_DATA:-$HOME/.z}" ] && {
+    echo "ERROR: z.sh's datafile (${_Z_DATA:-$HOME/.z}) is a directory."
+}
+
+_z() {
+
+    local datafile="${_Z_DATA:-$HOME/.z}"
+
+    # bail if we don't own ~/.z and $_Z_OWNER not set
+    [ -z "$_Z_OWNER" -a -f "$datafile" -a ! -O "$datafile" ] && return
+
+    # add entries
+    if [ "$1" = "--add" ]; then
+        shift
+
+        # $HOME isn't worth matching
+        [ "$*" = "$HOME" ] && return
+
+        # don't track excluded directory trees
+        local exclude
+        for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do
+            case "$*" in "$exclude*") return;; esac
+        done
+
+        # maintain the data file
+        local tempfile="$datafile.$RANDOM"
+        while read line; do
+            # only count directories
+            [ -d "${line%%\|*}" ] && echo $line
+        done < "$datafile" | awk -v path="$*" -v now="$(date +%s)" -F"|" '
+            BEGIN {
+                rank[path] = 1
+                time[path] = now
+            }
+            $2 >= 1 {
+                # drop ranks below 1
+                if( $1 == path ) {
+                    rank[$1] = $2 + 1
+                    time[$1] = now
+                } else {
+                    rank[$1] = $2
+                    time[$1] = $3
+                }
+                count += $2
+            }
+            END {
+                if( count > 9000 ) {
+                    # aging
+                    for( x in rank ) print x "|" 0.99*rank[x] "|" time[x]
+                } else for( x in rank ) print x "|" rank[x] "|" time[x]
+            }
+        ' 2>/dev/null >| "$tempfile"
+        # do our best to avoid clobbering the datafile in a race condition
+        if [ $? -ne 0 -a -f "$datafile" ]; then
+            env rm -f "$tempfile"
+        else
+            [ "$_Z_OWNER" ] && chown $_Z_OWNER:$(id -ng $_Z_OWNER) "$tempfile"
+            env mv -f "$tempfile" "$datafile" || env rm -f "$tempfile"
+        fi
+
+    # tab completion
+    elif [ "$1" = "--complete" -a -s "$datafile" ]; then
+        while read line; do
+            [ -d "${line%%\|*}" ] && echo $line
+        done < "$datafile" | awk -v q="$2" -F"|" '
+            BEGIN {
+                if( q == tolower(q) ) imatch = 1
+                q = substr(q, 3)
+                gsub(" ", ".*", q)
+            }
+            {
+                if( imatch ) {
+                    if( tolower($1) ~ tolower(q) ) print $1
+                } else if( $1 ~ q ) print $1
+            }
+        ' 2>/dev/null
+
+    else
+        # list/go
+        while [ "$1" ]; do case "$1" in
+            --) while [ "$1" ]; do shift; local fnd="$fnd${fnd:+ }$1";done;;
+            -*) local opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in
+                    c) local fnd="^$PWD $fnd";;
+                    e) local echo=echo;;
+                    h) echo "${_Z_CMD:-z} [-cehlrtx] args" >&2; return;;
+                    l) local list=1;;
+                    r) local typ="rank";;
+                    t) local typ="recent";;
+                    x) sed -i -e "\:^${PWD}|.*:d" "$datafile";;
+                esac; opt=${opt:1}; done;;
+             *) local fnd="$fnd${fnd:+ }$1";;
+        esac; local last=$1; [ "$#" -gt 0 ] && shift; done
+        [ "$fnd" -a "$fnd" != "^$PWD " ] || local list=1
+
+        # if we hit enter on a completion just go there
+        case "$last" in
+            # completions will always start with /
+            /*) [ -z "$list" -a -d "$last" ] && cd "$last" && return;;
+        esac
+
+        # no file yet
+        [ -f "$datafile" ] || return
+
+        local cd
+        cd="$(while read line; do
+            [ -d "${line%%\|*}" ] && echo $line
+        done < "$datafile" | awk -v t="$(date +%s)" -v list="$list" -v typ="$typ" -v q="$fnd" -F"|" '
+            function frecent(rank, time) {
+                # relate frequency and time
+                dx = t - time
+                if( dx < 3600 ) return rank * 4
+                if( dx < 86400 ) return rank * 2
+                if( dx < 604800 ) return rank / 2
+                return rank / 4
+            }
+            function output(files, out, common) {
+                # list or return the desired directory
+                if( list ) {
+                    cmd = "sort -n >&2"
+                    for( x in files ) {
+                        if( files[x] ) printf "%-10s %s\n", files[x], x | cmd
+                    }
+                    if( common ) {
+                        printf "%-10s %s\n", "common:", common > "/dev/stderr"
+                    }
+                } else {
+                    if( common ) out = common
+                    print out
+                }
+            }
+            function common(matches) {
+                # find the common root of a list of matches, if it exists
+                for( x in matches ) {
+                    if( matches[x] && (!short || length(x) < length(short)) ) {
+                        short = x
+                    }
+                }
+                if( short == "/" ) return
+                # use a copy to escape special characters, as we want to return
+                # the original. yeah, this escaping is awful.
+                clean_short = short
+                gsub(/\[\(\)\[\]\|\]/, "\\\\&", clean_short)
+                for( x in matches ) if( matches[x] && x !~ clean_short ) return
+                return short
+            }
+            BEGIN {
+                gsub(" ", ".*", q)
+                hi_rank = ihi_rank = -9999999999
+            }
+            {
+                if( typ == "rank" ) {
+                    rank = $2
+                } else if( typ == "recent" ) {
+                    rank = $3 - t
+                } else rank = frecent($2, $3)
+                if( $1 ~ q ) {
+                    matches[$1] = rank
+                } else if( tolower($1) ~ tolower(q) ) imatches[$1] = rank
+                if( matches[$1] && matches[$1] > hi_rank ) {
+                    best_match = $1
+                    hi_rank = matches[$1]
+                } else if( imatches[$1] && imatches[$1] > ihi_rank ) {
+                    ibest_match = $1
+                    ihi_rank = imatches[$1]
+                }
+            }
+            END {
+                # prefer case sensitive
+                if( best_match ) {
+                    output(matches, best_match, common(matches))
+                } else if( ibest_match ) {
+                    output(imatches, ibest_match, common(imatches))
+                }
+            }
+        ')"
+        [ $? -gt 0 ] && return
+        [ "$cd" ] || return
+        ${echo:-cd} "$cd"
+    fi
+}
+
+alias ${_Z_CMD:-z}='_z 2>&1'
+
+[ "$_Z_NO_RESOLVE_SYMLINKS" ] || _Z_RESOLVE_SYMLINKS="-P"
+
+if type compctl >/dev/null 2>&1; then
+    # zsh
+    [ "$_Z_NO_PROMPT_COMMAND" ] || {
+        # populate directory list, avoid clobbering any other precmds.
+        if [ "$_Z_NO_RESOLVE_SYMLINKS" ]; then
+            _z_precmd() {
+                _z --add "${PWD:a}"
+            }
+        else
+            _z_precmd() {
+                _z --add "${PWD:A}"
+            }
+        fi
+        [[ -n "${precmd_functions[(r)_z_precmd]}" ]] || {
+            precmd_functions[$(($#precmd_functions+1))]=_z_precmd
+        }
+    }
+    _z_zsh_tab_completion() {
+        # tab completion
+        local compl
+        read -l compl
+        reply=(${(f)"$(_z --complete "$compl")"})
+    }
+    compctl -U -K _z_zsh_tab_completion _z
+elif type complete >/dev/null 2>&1; then
+    # bash
+    # tab completion
+    complete -o filenames -C '_z --complete "$COMP_LINE"' ${_Z_CMD:-z}
+    [ "$_Z_NO_PROMPT_COMMAND" ] || {
+        # populate directory list. avoid clobbering other PROMPT_COMMANDs.
+        grep "_z --add" <<< "$PROMPT_COMMAND" >/dev/null || {
+            PROMPT_COMMAND="$PROMPT_COMMAND"$'\n''_z --add "$(command pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null)" 2>/dev/null;'
+        }
+    }
+fi

+ 41 - 0
shell/env

@@ -0,0 +1,41 @@
+# vim: ft=sh
+export EDITOR='vim'
+export PAGER='less'
+
+# Default Flags for `less`
+# e = quit at end of file
+# i = searches ignore case, unless there are upper case characters
+# r = display raw control characters (Ctrl-A is ^A)
+# M = verbose `less` prompt
+# X = disable termcap init/deinit strings - sometimes these clear the screen.
+export LESS='-iRMX'
+
+# Paths
+export PATH=$HOME/dotfiles/bin:$PATH
+export MANPATH=$MANPATH:$HOME/dotfiles/shell/man
+
+# Config Files: Which Are Sourced, And When?
+#
+# # BASH
+#
+# .bash_profile: interactive login
+# .bash_login: as above but lower preference
+# .profile: as above but lower preference
+# .bashrc: interactive non-login
+#
+# Interactive = not a script.
+# Login = shell that starts with a computer, or connection from tty or ssh.
+# Non-login = shell that starts from a terminal emulator or GUI.
+# So, put things mostly in `.bashrc`, source from `.bash_profile`.
+#
+# # ZSH
+#
+# .zshenv: (always)
+# .zprofile: [[ -o login ]]
+# .zshrc: [[ -o interactive ]]
+# .zlogin: [[ -o login ]]
+#
+# As seen, `zshenv` is sourced on all invocations of zsh. It is useful for
+# setting variables that should be available to other programs e.g. `PATH`,
+# `EDITOR`, `PAGER`, and generally not the place to put commands that produce
+# output or assume attachment to a tty/terminal.

+ 61 - 0
shell/man/man1/v.1

@@ -0,0 +1,61 @@
+.TH V "1" "February 2011" "v" "User Commands"
+
+.SH NAME
+v \- z for vim
+
+.SH SYNOPSIS
+v [\-a] [\-l] [\-[0\-9]] [\-\-debug] [\-\-help] [regex1 regex2 ... regexn]
+
+.SH AVAILABILITY
+bash, vim
+
+.SH INSTALLATION
+Put \fBv\fR somewhere in $PATH (e.g. /usr/local/bin/).
+.br
+For the manual page, put \fBv.1\fR somewhere in $MANPATH (e.g.
+/usr/local/man/man1/).
+
+.SH DESCRIPTION
+\fBv\fR uses viminfo's list of recently edited files to open one quickly no
+matter where you are in the filesystem.
+.P
+By default, it will open the most recently edited file matching all of the
+provided regular expressions.
+
+.SH OPTIONS
+\fB\-a\fR           don't skip deleted files
+.br
+\fB\-l\fR           when multiple matches, show a list
+.br
+\fB\-[0\-9]\fR       edit nth most recent file
+.br
+\fB\--debug\fR      dry run
+.br
+\fB\--help\fR       show a brief help message
+
+.SH EXAMPLES
+\fBv\fR            list and choose from all files
+.br
+\fBv -0\fR         reopen most recently edited file
+.br
+\fBv foo bar\fR    edit first file matching foo and bar
+.br
+\fBv -l foo bar\fR list and choose files matching foo and bar
+
+.SH NOTES
+Shell variables, such as $, must be escaped if used in regular expressions.
+
+\fBBehavior\fR
+.br
+The default behavior is to open the most recent file that matches the search
+terms, even if there are multiple matches.
+
+You may find it useful to alias vl='v -l'. When there are multiple matches,
+this will prompt for a choice, rather than editing the first match. The author
+is still not sure which behavior should be the default, and has chosen one 
+provisionally.
+
+.SH SEE ALSO
+vim(1), regex(7)
+.P
+Please file bugs at https://github.com/rupa/v/

+ 169 - 0
shell/man/man1/z.1

@@ -0,0 +1,169 @@
+.TH "Z" "1" "January 2013" "z" "User Commands"
+.SH
+NAME
+z \- jump around
+.SH
+SYNOPSIS
+z [\-chlrtx] [regex1 regex2 ... regexn]
+.SH
+AVAILABILITY
+bash, zsh
+.SH
+DESCRIPTION
+Tracks your most used directories, based on 'frecency'.
+.P
+After a short learning phase, \fBz\fR will take you to the most 'frecent'
+directory that matches ALL of the regexes given on the command line, in order.
+
+For example, \fBz foo bar\fR would match \fB/foo/bar\fR but not \fB/bar/foo\fR.
+.SH
+OPTIONS
+.TP
+\fB\-c\fR
+restrict matches to subdirectories of the current directory
+.TP
+\fB\-e\fR
+echo the best match, don't cd
+.TP
+\fB\-h\fR
+show a brief help message
+.TP
+\fB\-l\fR
+list only
+.TP
+\fB\-r\fR
+match by rank only
+.TP
+\fB\-t\fR
+match by recent access only
+.TP
+\fB\-x\fR
+remove the current directory from the datafile
+.SH EXAMPLES
+.TP 14
+\fBz foo\fR
+cd to most frecent dir matching foo
+.TP 14
+\fBz foo bar\fR
+cd to most frecent dir matching foo, then bar
+.TP 14
+\fBz -r foo\fR
+cd to highest ranked dir matching foo
+.TP 14
+\fBz -t foo\fR
+cd to most recently accessed dir matching foo
+.TP 14
+\fBz -l foo\fR
+list all dirs matching foo (by frecency)
+.SH
+NOTES
+.SS
+Installation:
+.P
+Put something like this in your \fB$HOME/.bashrc\fR or \fB$HOME/.zshrc\fR:
+.RS
+.P
+\fB. /path/to/z.sh\fR
+.RE
+.P
+\fBcd\fR around for a while to build up the db.
+.P
+PROFIT!!
+.P
+Optionally:
+.RS
+Set \fB$_Z_CMD\fR to change the command name (default \fBz\fR).
+.RE
+.RS
+Set \fB$_Z_DATA\fR to change the datafile (default \fB$HOME/.z\fR).
+.RE
+.RS
+Set \fB$_Z_NO_RESOLVE_SYMLINKS\fR to prevent symlink resolution.
+.RE
+.RS
+Set \fB$_Z_NO_PROMPT_COMMAND\fR to handle \fBPROMPT_COMMAND/precmd\fR yourself.
+.RE
+.RS
+Set \fB$_Z_EXCLUDE_DIRS\fR to an array of directory trees to exclude.
+.RE
+.RS
+Set \fB$_Z_OWNER\fR to allow usage when in 'sudo -s' mode.
+.RE
+.RS
+(These settings should go in .bashrc/.zshrc before the line added above.)
+.RE
+.RS
+Install the provided man page \fBz.1\fR somewhere like \fB/usr/local/man/man1\fR.
+.RE
+.SS
+Aging:
+The rank of directories maintained by \fBz\fR undergoes aging based on a simple
+formula. The rank of each entry is incremented every time it is accessed. When
+the sum of ranks is over 9000, all ranks are multiplied by 0.99. Entries with a
+rank lower than 1 are forgotten.
+.SS
+Frecency:
+Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted rank
+that depends on how often and how recently something occurred. As far as I
+know, Mozilla came up with the term.
+.P
+To \fBz\fR, a directory that has low ranking but has been accessed recently
+will quickly have higher rank than a directory accessed frequently a long time
+ago.
+.P
+Frecency is determined at runtime.
+.SS
+Common:
+When multiple directories match all queries, and they all have a common prefix,
+\fBz\fR will cd to the shortest matching directory, without regard to priority.
+This has been in effect, if undocumented, for quite some time, but should
+probably be configurable or reconsidered.
+.SS
+Tab Completion:
+\fBz\fR supports tab completion. After any number of arguments, press TAB to
+complete on directories that match each argument. Due to limitations of the
+completion implementations, only the last argument will be completed in the
+shell.
+.P
+Internally, \fBz\fR decides you've requested a completion if the last argument
+passed is an absolute path to an existing directory. This may cause unexpected
+behavior if the last argument to \fBz\fR begins with \fB/\fR.
+.SH
+ENVIRONMENT
+A function \fB_z()\fR is defined.
+.P
+The contents of the variable \fB$_Z_CMD\fR is aliased to \fB_z 2>&1\fR. If not
+set, \fB$_Z_CMD\fR defaults to \fBz\fR.
+.P
+The environment variable \fB$_Z_DATA\fR can be used to control the datafile
+location. If it is not defined, the location defaults to \fB$HOME/.z\fR.
+.P
+The environment variable \fB$_Z_NO_RESOLVE_SYMLINKS\fR can be set to prevent
+resolving of symlinks. If it is not set, symbolic links will be resolved when
+added to the datafile.
+.P
+In bash, \fBz\fR appends a command to the \fBPROMPT_COMMAND\fR environment
+variable to maintain its database. In zsh, \fBz\fR appends a function
+\fB_z_precmd\fR to the \fBprecmd_functions\fR array.
+.P
+The environment variable \fB$_Z_NO_PROMPT_COMMAND\fR can be set if you want to
+handle \fBPROMPT_COMMAND\fR or \fBprecmd\fR yourself.
+.P
+The environment variable \fB$_Z_EXCLUDE_DIRS\fR can be set to an array of
+directory trees to exclude from tracking. \fB$HOME\fR is always excluded.
+Directories must be full paths without trailing slashes.
+.P
+The environment variable \fB$_Z_OWNER\fR can be set to your username, to
+allow usage of \fBz\fR when your sudo enviroment keeps \fB$HOME\fR set.
+.SH
+FILES
+Data is stored in \fB$HOME/.z\fR. This can be overridden by setting the
+\fB$_Z_DATA\fR environment variable. When initialized, \fBz\fR will raise an
+error if this path is a directory, and not function correctly.
+.P
+A man page (\fBz.1\fR) is provided.
+.SH
+SEE ALSO
+regex(7), pushd, popd, autojump, cdargs
+.P
+Please file bugs at https://github.com/rupa/z/

+ 54 - 0
shell/zsh/completion.zsh

@@ -0,0 +1,54 @@
+# Relevant manpages:
+# - zshoptions
+# - zshcompwid
+# - zshcompsys
+
+
+setopt auto_menu        # show completion menu on succesive tab press
+setopt complete_in_word # Perform completion from both ends of a word
+setopt always_to_end    # Move cursor to end of word after completion
+
+
+# Add custom completions from dotfiles to fpath.
+fpath=(~/dotfiles/shell/zsh/completions $fpath)
+
+
+# Load completion functions
+#
+# `compinit` - Completion initisation.
+# `compaudit` - Finds insecure completion folders (wrong owner, 777) in fpath.
+# Used by compinit internally, added here to be called manually.
+# `compinstall` - Completion configurator.
+autoload -Uz compinit compaudit #compinstall
+
+
+# Initialise completion
+compinit
+
+
+# Load zsh module for completion listing extensions.
+# Enables extensions for match highlighting, list scrolling and different
+# completion menu styles.
+zmodload -i zsh/complist
+
+
+#?
+WORDCHARS=''
+
+
+# Case-insensitive completion.
+zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'
+
+
+# Completion Waiting Dots
+expand-or-complete-with-dots() {
+  # toggle line-wrapping off and back on again
+  [[ -n "$terminfo[rmam]" && -n "$terminfo[smam]" ]] && echoti rmam
+  print -Pn "%{$FG[1]......$cReset%}"
+  [[ -n "$terminfo[rmam]" && -n "$terminfo[smam]" ]] && echoti smam
+
+  zle expand-or-complete
+  zle redisplay
+}
+zle -N expand-or-complete-with-dots
+bindkey "^I" expand-or-complete-with-dots

+ 32 - 0
shell/zsh/completions/_cu

@@ -0,0 +1,32 @@
+#compdef cu
+
+# Completion for the Call Up (cu) command
+#
+# More info:
+# `man zshcompsys`
+# http://www.linux-mag.com/id/1106/
+
+# Standard completion variables:
+local state  # Gets set to a value when an argument action starts with `->`
+
+# Argument line format:
+# (option-exclusions)option[description]:Help-text-in-verbose-mode:action
+#
+# `option` can be followed by a symbol (e.g. `+`, `=`) to define how option
+# values can be written . Look up `_arguments` in the `zshcompsys` manpage.
+#
+# `action` can be blank, a list of values, or additional completion logic.
+#
+_arguments \
+  "-s+[baud rate]:Symbol transfer speed, depending on device:(9600)" \
+  "-l+[call-up line to listen to]:Device starting with 'cu':->line"
+
+case $state in
+  (line)
+    # `_files` is a general filepath completion helper
+    # `-W` = working directory
+    # `-g` = regexp filter
+    # All completion actions should return `0` on success and non-zero on error.
+    _files -W /dev -g 'cu\.*' && return 0
+    ;;
+esac

+ 30 - 0
shell/zsh/completions/_tmuxinator

@@ -0,0 +1,30 @@
+#compdef tmuxinator mux
+
+_tmuxinator() {
+  local commands projects
+  commands=(${(f)"$(tmuxinator commands zsh)"})
+  projects=(${(f)"$(tmuxinator completions start)"})
+
+  if (( CURRENT == 2 )); then
+    _describe -t commands "tmuxinator subcommands" commands
+    _describe -t projects "tmuxinator projects" projects
+  elif (( CURRENT == 3)); then
+    case $words[2] in
+      copy|debug|delete|open|start)
+        _arguments '*:projects:($projects)'
+      ;;
+    esac
+  fi
+
+  return
+}
+
+_tmuxinator
+
+# Local Variables:
+# mode: Shell-Script
+# sh-indentation: 2
+# indent-tabs-mode: nil
+# sh-basic-offset: 2
+# End:
+# vim: ft=zsh sw=2 ts=2 et

+ 6 - 0
shell/zsh/correction.zsh

@@ -0,0 +1,6 @@
+alias man='nocorrect man'
+alias mkdir='nocorrect mkdir'
+alias mv='nocorrect mv'
+alias sudo='nocorrect sudo'
+
+setopt correct_all

+ 3 - 0
shell/zsh/cu.zsh

@@ -0,0 +1,3 @@
+# Serial console access ("call up line"). Type `~.` (and wait) to disconnect.
+alias cul='sudo cu -s 9600 -l'
+compdef _cu cul

+ 30 - 0
shell/zsh/directories.zsh

@@ -0,0 +1,30 @@
+alias md='mkdir -p'
+alias rd=rmdir
+
+# Multiple directory returns. Usable anywhere in command (-g).
+alias -g ...='../..'
+alias -g ....='../../..'
+alias -g .....='../../../..'
+alias -g ......='../../../../..'
+
+# Automatic directory stack population.
+setopt auto_pushd
+setopt pushd_ignore_dups
+setopt pushdminus
+
+# Aliases for using the directory stack.
+alias d='dirs -v | head -10'
+alias -- -='cd -'
+alias 1='cd -'
+alias 2='cd -2'
+alias 3='cd -3'
+alias 4='cd -4'
+alias 5='cd -5'
+alias 6='cd -6'
+alias 7='cd -7'
+alias 8='cd -8'
+alias 9='cd -9'
+
+# Manually use the directory stack.
+alias pu='pushd'
+alias po='popd'

+ 1 - 0
shell/zsh/games.zsh

@@ -0,0 +1 @@
+autoload -U tetriscurses

+ 28 - 0
shell/zsh/grep.zsh

@@ -0,0 +1,28 @@
+# is x grep argument available?
+grep-flag-available() {
+    echo | grep $1 "" >/dev/null 2>&1
+}
+
+GREP_OPTIONS=""
+
+# color grep results
+if grep-flag-available --color=auto; then
+    GREP_OPTIONS+=" --color=auto"
+fi
+
+# ignore VCS folders (if the necessary grep flags are available)
+VCS_FOLDERS="{.bzr,CVS,.git,.hg,.svn}"
+
+if grep-flag-available --exclude-dir=.cvs; then
+    GREP_OPTIONS+=" --exclude-dir=$VCS_FOLDERS"
+elif grep-flag-available --exclude=.cvs; then
+    GREP_OPTIONS+=" --exclude=$VCS_FOLDERS"
+fi
+
+# export grep settings
+alias grep="grep $GREP_OPTIONS"
+
+# clean up
+unset GREP_OPTIONS
+unset VCS_FOLDERS
+unfunction grep-flag-available

+ 18 - 0
shell/zsh/history.zsh

@@ -0,0 +1,18 @@
+## Command history configuration
+HISTSIZE=10000 # Max events stored in the internal history list.
+SAVEHIST=10000 # Max events to save to file when interactive shell exits.
+[[ -z "$HISTFILE" ]] && HISTFILE=$HOME/.zshhistory
+
+# Show history
+#alias history='fc -l 1' # No timestamps
+alias history='fc -il 1' # timestamp "yyyy-mm-dd hh:mm"
+alias h='history | tail -n 100'
+
+setopt append_history
+setopt extended_history
+setopt hist_expire_dups_first
+setopt hist_ignore_dups # ignore duplication command history list
+setopt hist_ignore_space
+setopt inc_append_history
+setopt nohistverify # Don't show expansions, just execute, e.g. for !! and !$
+setopt nosharehistory # Don't have the same history across tabs/windows

+ 66 - 0
shell/zsh/key-bindings.zsh

@@ -0,0 +1,66 @@
+# Relevant manpages:
+# - zshzle - binding, widgets
+# - zshbuiltins - autoload
+# - zshmodules - echoti, $terminfo
+# - terminfo - "smkx" and "rmkx"
+
+
+# Use vi-bindings in ZLE (Zsh Line Editor) for command editing
+bindkey -v
+
+
+# <S-Tab> to cycle backwards through autocomplete suggestions.
+bindkey '^[[Z' reverse-menu-complete
+
+
+# Command line editing with text editor: `v` in vi-command mode or `<C-x><C-e>`.
+#
+# Load the `edit-command-line` function from an fpath folder.
+# e.g. /usr/share/zsh/*/functions
+autoload -Uz edit-command-line
+zle -N edit-command-line
+# `-N` creates a New user-defined widget that fires a given function.
+# OR if no function is given, the widget will fire a function of the same name.
+bindkey '\C-x\C-e' edit-command-line # Bind the widget to a key.
+bindkey -M vicmd v edit-command-line # Same, for a given keymap (vicmd).
+
+
+# Make Ctrl-z also bring jobs back to the foreground.
+fancy-ctrl-z () {
+  # Push current input into an input stack. Gets popped when ZLE next opens
+  # e.g. at next <C-z>, or when foreground jobs finishes.
+  [[ $#BUFFER -ne 0 ]] && zle push-input
+  # Bring last job back to foreground
+  BUFFER="fg"
+  zle accept-line
+}
+zle -N fancy-ctrl-z
+bindkey '^Z' fancy-ctrl-z
+
+
+# Key bindings for history search
+#
+# Start typing + [Up-Arrow] - fuzzy find history forward
+if [[ "${terminfo[kcuu1]}" != "" ]]; then
+  bindkey "${terminfo[kcuu1]}" up-line-or-search
+fi
+# Start typing + [Down-Arrow] - fuzzy find history backward
+if [[ "${terminfo[kcud1]}" != "" ]]; then
+  bindkey "${terminfo[kcud1]}" down-line-or-search
+fi
+# [Ctrl-r] - Search backward incrementally for a specified string.
+bindkey '^r' history-incremental-search-backward
+
+
+# If a terminal supports it, make sure to enter `keyboard_transmit` mode when
+# zle is active, since only then are values from `$terminfo` valid.
+if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then
+  function zle-line-init() {
+    echoti smkx # Set the terminal into `keyboard_transmit` mode
+  }
+  function zle-line-finish() {
+    echoti rmkx # Leave `keyboard_transmit` mode
+  }
+  zle -N zle-line-init   # Special Widget: runs before each new line of input.
+  zle -N zle-line-finish # Special Widget: runs after input has been read.
+fi

+ 2 - 0
shell/zsh/tabtitle.zsh

@@ -0,0 +1,2 @@
+# Set tab title to hostname
+print -Pn "\e]1;`hostname | cut -d. -f1`\a"

+ 13 - 0
shell/zsh/vi-mode.zsh

@@ -0,0 +1,13 @@
+# Indicate if ZLE is in vicmd mode/keymap.
+VIMODE_INDICATOR="$cBold${FG[2]}NORMAL$cReset"
+function vimode() {
+  # Display indicator if the keymap is 'vicmd', otherwise nothing.
+  echo "${${KEYMAP/vicmd/$VIMODE_INDICATOR}/(main|viins)/}"
+}
+
+# Refresh the prompt each keymap change, to show/hide the vicmd indicator.
+function zle-keymap-select {
+  zle reset-prompt # Re-expand the prompts.
+  zle -R           # Redisplay the command line.
+}
+zle -N zle-keymap-select # Special Widget: called when keymap changes.

+ 20 - 0
shell/zshrc

@@ -0,0 +1,20 @@
+[[ -r ~/.zshlocal ]] && source ~/.zshlocal
+
+source ~/dotfiles/shell/common/load
+
+for file in ~/dotfiles/shell/zsh/*.zsh; do
+  source $file
+done
+
+# Command Prompt
+#
+#     [host]  directory  gitinfo  jobinfo
+#     user -                                           vimode time histeventno
+#
+setopt PROMPT_SUBST
+PROMPT='
+$FG[6][%m]  $FG[3]%3~$(__gitp "  %s")%(1j.  $FG[63][jobs]: $FG[1]%j.)
+$FG[5]%n - $cReset'
+RPROMPT='$(vimode) $cReset%T $FG[7]%h$cReset'
+
+[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh