Просмотр исходного кода

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 9 лет назад
Родитель
Сommit
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