# zdirs -- Directory stack manipulation functions for zsh
#	Brian Katzung	20 March 1993
#
# Csh compatible:
# dirs [-l]		Stack listing (long)
# popd			Cd to top of stack and pop
# popd +n		Pop the nth item from the stack
# pushd			Swap cwd and top of stack
# pushd +n		Move cwd and 1..n-1 to end; cd top and pop
# pushd directory	Push cwd and cd to directory
#
# Extensions:
# dirs [-muv]		Stack listing (implement move, implement unique,
#			list vertically)
# sd			pushd -mn
# pd			pushd -n
# pushd . pattern	Push the first directory containing pattern
# pushd -n +n		Push cwd and cd to former nth item
# pushd oldpat newpat	push cwd and cd oldpath newpat

#DIRS=
# WORKAROUND ("case $something in" may expand to more than three tokens!)
case "$HOME" in
/)	# Home is /; no ~ substitution
	dirsmap= ;;
*)	# Replace $HOME with ~ by default
	dirsmap=("$HOME" "~") ;;
esac

# Utility functions to avoid having to exec cut, expr, sed, etc.

# Set stack to directories not matching the first argument
_d_except ()
{
	local _which="$1" _dir=

	shift
	dirs=
	for _dir
	do
		case "$_dir" in
		$_which)
			;;
		*)	dirs=(${=dirs} "$_dir") ;;
		esac
	done
	return 0
}

# Returns arguments except the first match
_d_notfirst ()
{
	local _which="$1" _dir=

	shift
	dirs=
	for _dir
	do
		case "$_dir" in
		# At the first match, print everything after the match
		$_which)
			shift
			dirs=(${=dirs} "$@")
			return 0
			;;
		esac

		# Print each item before the first match
		dirs=(${=dirs} "$_dir")
		shift
	done
	return 0
}

# Display the directory stack
dirs ()
{
	local _d_flags= _dir= _ind= _from= _to=

	# Accumulate flags
	for _dir
	do
		case "$_dir" in
		-*)	_d_flags="$_d_flags$_dir"
			shift
			;;
		*)	break ;;
		esac
	done

	# Implement -m (move mode) and -u (unique mode)
	case "$_d_flags" in
	*u*)	_d_except "$PWD" ${=dirs} ;;
	*m*)	_d_notfirst "$PWD" ${=dirs} ;;
	esac

	# Set up for short or long format
	case "$_d_flags" in
	*l*)	# Long listing (no ~ substitution)
		set -- "$PWD" ${=dirs}
		;;
	*)	# Shortened listing
		shift $#
		for _dir in "$PWD" ${=dirs}
		do
			(( _ind = 1 ))
			while (( $_ind < $#dirsmap ))
			do
				_from=$dirsmap[$_ind]
				(( ++_ind ))
				_to=$dirsmap[$_ind]
				(( ++_ind ))
				eval _dir=\${_dir:s"$_from\\$_to"}
			done
			# WORKAROUND (${1+"$@"} -> "" if $# == 0!)
			set -- ${1:+"$@"} "$_dir"
		done
		;;
	esac

	# Print in horizontal or vertical format
	case "$_d_flags" in
	*v*)	# Vertical listing (1/line)
		for _dir
		do echo "$_dir"
		done
		;;
	*)	# Horizontal
		echo "$*"
		;;
	esac
	return 0
}

# Push directory or rearrange the directory stack
pushd ()
{
	local _d_flags= _dir= _pwd=

	# Accumulate flags
	for _dir
	do
		case "$_dir" in
		-*)	_d_flags="$_d_flags$_dir"
			shift
			;;
		*)	break ;;
		esac
	done

	if (( $# > 2 ))
	then
		echo "Too many arguments to pushd."
		return 1
	fi

	_pwd="$PWD"
	case "${1-}" in
	"")	# Swap top two directories
		if (( $#dirs == 0 ))
		then
			echo "No other directory."
			return 1
		fi

		if cd "$dirs[1]"
		then
			# New location off stack and old one on
			set -- ${=dirs}
			dirs=("$_pwd" ${=@[2,$#]})
		fi
		;;

	+[0-9]*)# Rearrange nth or nth..last item(s) on the stack
		if (( $1 > $#dirs ))
		then	echo "Directory stack not that deep."
			return 1
		fi

		if cd "$dirs[$1]"
		then
			case "$_d_flags" in
			*n*)	# New behavior - just push nth item
				dirs=("$_pwd" ${=dirs})
				;;
			*)	# Move cwd and 1..n-1 to end
				dirs=("$_pwd" ${=dirs})
				set -- $1 $[$1+2] $#dirs
				dirs=(${=dirs[$2,$3]} ${=dirs[1,$1]})
				;;
			esac
		fi
		;;

	.)	case "${2-}" in
		"")	# pushd . (backward compatibility)
			dirs=("$_pwd" ${=dirs})
			;;
		*)	# Push first stack item containing $2
			for _dir in ${=dirs}
			do
				case "$_dir" in
				*"$2"*)	_pwd="$PWD"
					if cd "$_dir"
					then
						dirs=("$_pwd" ${=dirs})
					fi
					break
					;;
				esac
			done
			;;
		esac
		;;

	*)	# Push new directory
		_pwd="$PWD"
		if cd "$1" ${2:+"$2"}
		then
			dirs=("$_pwd" ${=dirs})
		fi
		;;
	esac

	dirs $_d_flags
	return 0
}

# Remove the top directory or a middle directory from the directory stack
popd ()
{
	local _d_flags= _dir=

	for _dir
	do
		case "$_dir" in
		-*)	_d_flags="$_d_flags$_dir"
			shift
			;;
		*)	break ;;
		esac
	done

	if (( $# > 1 ))
	then
		echo "Too many arguments to popd."
		return 1
	fi

	case "${1-}" in
	"")	# Pop the top directory
		if (( $#dirs == 0 ))
		then
			echo "Directory stack empty."
			return 1
		fi

		if cd "$dirs[1]"
		then
			set -- ${=dirs}
			dirs=(${=dirs[2,$#]})
		fi
		;;

	+[0-9]*)# Discard the nth item from the stack
		if (( $1 > $#dirs ))
		then
			echo "Directory stack not that deep."
			return 1
		fi
		set -- $[$1-1] $[$1+1] $#dirs
		if (( $1 == 0 ))
		then
			# WORKAROUND ($dirs[1,0] gives $dirs[1]!)
			dirs=(${=dirs[$2,$3]})
		else
			dirs=(${=dirs[1,$1]} ${=dirs[$2,$3]})
		fi
		;;
	esac

	dirs $_d_flags
	return 0
}

# Shorthand functions

sd ()
{
	pushd -mn ${1:+"$@"}
	return $?
}

pd ()
{
	pushd -n ${1:+"$@"}
	return $?
}
