#!/bin/bash
### Curby's Directory Changer (CDC)
### v1.01.00, 2006-06-05

###  WTF?
#
#  CDC tracks the most recent directory changes.  Type `cdc -h` or
#  `cdc --help` (or look below) for help.
#
#  If CDC is used as a cuvlis plugin, place this file into the plugins
#  directory and set the executable bit to enable it.  Otherwise, 
#  source it from your bash initialization scripts.
#

###   License
#
#  Copyright (C) 2006 Michael Lee <kirbysdl@hotmail.com>
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

###  Changelog
#
#  2006-06-05, v1.01.00  + Added longname shortening
#                        + Added echoing of $PWD for all invocations of cd
#                        + Added error check for cdn
#                        * Misc. tweaks and fixes
#
#  2006-06-03, v1.00.00  + First version, works
#

echo -en ${LINC}"|  |- "${TXTC}"CDC... "${TXT2}"("

##
# Setup and initialization
#

# Size of history plus one for the current directory
export CUVLIS_CDC=16
# Set to "ON" for pretty-printing, any other value for plain printing
export CUVLIS_CDC_PRETTY="ON"
# Set to "ON" to retain history through invocation, set to "OFF" for
# release.  It can cause unexpected behavior when left ON
export CUVLIS_CDC_TEST="OFF"

# Initialize directory history, assuming pwd will be the wd that the
# user sees at the prompt
if [[ "z$CUVLIS_CDC_TEST" != "zON" ]]; then
  unset OLDPWDS
fi
OLDPWDS[0]=$PWD

# Add verbosity
echo -n $(($CUVLIS_CDC-1)) dirs
if [[ "z$CUVLIS_CDC_PRETTY" == "zON" ]]; then
  echo -n ', pretty'
fi
echo -e ') '${DONC}Done



##
# Dummy cdc function to display help or call cd
#
cdc () {
  if [[ "z$1" == "z-h" || "z$1" == "z--help" ]]; then
    # echo doesn't work with heredocs
    cat <<EOHD
Curby's Directory Changer (CDC)

  These scripts keep track of your directory history, so changing to
  previous working directories is faster.

Usage:
  cd [-L|-P] [dir]
    Changes directories just like the builtin cd.  See the cd manpage.

  cdc [-h|--help|[-L|-P] [dir]]
    Changes directories just like the builtin cd, and adds help flags.

  cdn eid
    Changes the current directory to the directory identified by eid
    in the history, so \`cdn 1\` changes to the last directory used.

  cn
    For n from 1 to 9 inclusive, there are aliases to cdn, so \`c1\`
    and \`cdn 1\` both change to the last directory.  The cdn command
    should be used explicitly for history entries older than 9.

  cl [-l]
    Lists the current working directory and the history of previous
    working directories.  Long names are shortened by default; this
    behavior can be disabled with the '-l' argument.

Further help:
  See the README file distributed with the CDC source, and available
  separately at http://curby.net/filelib/cdc/README
EOHD
    return 0
  fi
  cd "$@"
}



##
# Custom cd Function
#
# Overloads the bash builtin command with a version that updates the
# history of directory changes.
#
cd () {
  # Attempt directory change first
  # 'builtin' avoids recursive calls
  # Quotes are for directories with spaces in their names
  builtin cd "$@"
  # We can't keep track of the return value without a var
  local result=$?
  if [[ "z$@" == "z.." || "z$@" == "z" || "${1:0:1}" == "/" ]]; then
    echo "$PWD"
  fi

  # Quit on error or if no directory change occurs
  if [[ $result -ne 0 || "z$PWD" == "z$OLDPWD" ]]; then
    return $result
  fi

  # Now update history using $PWD

  # Current size of history (may be less than max size)
  local numpwds=${#OLDPWDS[*]}
  local i=0

  # Attempt to find a match in the history
  while (( $i < $numpwds )); do
    if [[ "z${OLDPWDS[i]}" == "z$PWD" ]]; then
      # Update history by moving the match to the top
      CDCShift $i
      return 0
    fi
    let i=i+1
  done

  # Nothing matches, need to add the new directory
  if (( $numpwds == $CUVLIS_CDC )); then
    let i=i-1
  fi
  CDCShift $i
  return 0
}



##
# cdn Function
#
# Changes to a particular directory in the history
#
cdn () {
  # Check that we've got a good arg
  if [[ $1 -eq 0 ]]; then
    echo CDC: Yikes, I need a number for something in the history
    return 2
  fi
  # Current size of history (may be less than max size)
  local numpwds=${#OLDPWDS[*]}

  # Check for an out-of-bounds error
  if (( $numpwds < 2 )); then
    echo CDC: Nothing in the history
    return 3
  elif (( $1 > $numpwds-1 )); then
    echo CDC: $1 not in history, last entry is $(($numpwds-1))
    return 4
  fi

  cd "${OLDPWDS[$1]}"
}

# Make aliases to cdn
limit=9
if (( $CUVLIS_CDC <= $limit )); then
  let limit=CUVLIS_CDC-1
fi
while (( $limit > 0 )); do
  alias c$limit="cdn $limit"
  let limit=limit-1
done
# Don't leave vars lying around in the user's environment
unset limit



##
# cl Function
#
# Lists the directories in the history
#
cl () {
  # Current size of history (may be less than max size)
  local numpwds=${#OLDPWDS[*]}
  # Width of first column (increase it if you're crazy enough to want
  # to track 1000 directories or more)
  local width=3
  local i=0
  local temp="not much"

  # Set colors if we're doing pretty printing
  if [[ "z$CUVLIS_CDC_PRETTY" == "zON" ]]; then
    # 31red 32grn 33ylw 34blu 35mag 36cyan 37wht
    local CBLD="\033[1m"     ;#       bold
    local CDCZ="\033[0;33m"  ;# Z for zero-th entry (pwd)
    local CDCS="\033[0;32m"  ;# S for shortcut
    local CDCO="\033[0;35m"  ;# O for other entries
    local CDCC="\033[0m"     ;# C for clear
  fi

  # Print status header (ugly, but probably faster than alternatives)
  if (( $numpwds == 2 )); then
    echo CDC is tracking $(($numpwds-1)) directory \($(($CUVLIS_CDC-1)) max\)
  else
    echo CDC is tracking $(($numpwds-1)) directories \($(($CUVLIS_CDC-1)) max\)
  fi
  # Print pwd and history
  while (( $i < $numpwds )); do
    if (( $i < 1 )); then
      printf "${CDCZ}%${width}s" "pwd"
    elif (( $i < 10 )); then
      printf "${CDCS}%${width}s" "c$i"
    else
      printf "${CDCO}%${width}s" "$i"
    fi
    temp=${OLDPWDS[i]/#${HOME}/~}
    if [[ "z$1" != "z-l" && ${#temp} -gt 70 ]]; then
      temp=${temp:0:34}${CDCC}...${CBLD}${temp: -33}
    fi
    echo -e ': '${CDCC}${CBLD}$temp${CDCC}
    let i=i+1
  done

  # A much simpler thing would be this quick two-liner:
  #   local IFS=$'\n'; echo "${OLDPWDS[*]}"
  # but that would be TOO easy
}



##
# Internal-use function to update arrays
#
CDCShift () {
  local i=$1
  local j
  while (( $i > 0 )); do
    let j=i-1
    OLDPWDS[i]=${OLDPWDS[j]}
    let i=j
  done
  OLDPWDS[0]=$PWD
}

