#!/bin/sh

# Script for using ViM as a PAGER.
# Based on Bram's less.sh.
# Version 1.8.9
# git://github.com/rkitover/vimpager.git

# Just pass through if not on a tty
if [ ! -t 1 ]; then
	exec cat "${@}"
fi

# try to find a better shell, especially on Solaris

PATH=$PATH:/usr/local/bin:/opt/csw/bin:/opt/local/bin:/usr/dt/bin:/usr/xpg4/bin:/usr/bin:/bin

if [ -z "$IN_BASH" ] && command -v bash >/dev/null; then
	IN_BASH=1
	export IN_BASH
	exec bash "$0" "$@"
elif [ -z "$IN_BASH" ] && [ -z "$IN_KSH" ]; then
	if command -v dtksh >/dev/null; then
		IN_KSH=1
		export IN_KSH
		exec dtksh "$0" "$@"
	elif [ -x /usr/xpg4/bin/sh ]; then
		IN_KSH=1
		export IN_KSH
		exec /usr/xpg4/bin/sh "$0" "$@"
	elif command -v ksh93 >/dev/null; then
		IN_KSH=1
		export IN_KSH
		exec ksh93 "$0" "$@"
	elif command -v ksh >/dev/null; then
		IN_KSH=1
		export IN_KSH
		exec ksh "$0" "$@"
	fi
fi

# hopefully we're now POSIX

case $(uname -s) in
	Linux) linux=1 ;;
	SunOS) solaris=1 ;;
        Darwin) osx=1; bsd=1 ;;
	CYGWIN*) cygwin=1 ;;
	MINGW*) msys=1 ;;
	OpenBSD) openbsd=1; bsd=1 ;;
	FreeBSD) freebsd=1; bsd=1 ;;
	NetBSD) netbsd=1; bsd=1 ;;
	*) bsd=1 ;;
esac

# grep -q is often not available
grep_q() {
	_pat=$1
	shift
	awk '
		BEGIN { exit_val = 1 }
		$0 ~ /'"$_pat"'/ { exit_val = 0; exit(exit_val) }
		END { exit(exit_val) }
	' "$@"
}

head_n() {
	_lines=$1
	shift

	if [ -z "$_head_syntax" ]; then
		_head1_test=`echo xx | head -n 1 2>/dev/null`
		if [ "$_head1_test" = "xx" ]; then
			_head_syntax=new
		else
			_head_syntax=old
		fi
	fi

	if [ "$_head_syntax" = "new" ]; then
		head -n $_lines "$@"
	else
		head -$_lines "$@"
	fi
}

tail_n() {
	_lines=$1
	shift

	if [ -z "$_tail_syntax" ]; then
		_tail1_test=`echo xx | tail -n 1 2>/dev/null`
		if [ "$_tail1_test" = "xx" ]; then
			_tail_syntax=new
		else
			_tail_syntax=old
		fi
	fi

	if [ "$_tail_syntax" = "new" ]; then
		tail -n $_lines "$@"
	else
		tail -$_lines "$@"
	fi
}

# Use the real TEMP directory on windows in case we are using a native vim/gvim
tmp=/tmp
[ -n "${msys}" -o -n "${cygwin}" ] && tmp=$(printf "%s\n" "${TEMP}" | tr '\\' /)
# Create a safe directory in which we place all other tempfiles.
tmp="${tmp}/vimpager_${$}"
mkdir -m 700 "${tmp}" || {
    echo Could not create temporary directory ${tmp} >&2
    exit 1
}
trap "rm -rf ${tmp}" HUP INT QUIT ILL TRAP KILL BUS TERM

# Detect terminal size
if command -v tput >/dev/null; then
        # this is the only way it works on Cygwin
	tput cols  > "${tmp}/vimpager_cols_${$}"
	tput lines > "${tmp}/vimpager_lines_${$}"

	cols=$(cat "${tmp}/vimpager_cols_${$}")
	lines=$(cat "${tmp}/vimpager_lines_${$}")

	rm -f "${tmp}/vimpager_cols_${$}" "${tmp}/vimpager_lines_${$}"
fi

# msys has no tput, this doesn't work on Cygwin by the way
if [ -z "${cols}" ] && command -v bash >/dev/null; then
	cols=$(bash -i -c 'echo ${COLUMNS}')
	lines=$(bash -i -c 'echo ${LINES}')
fi

# If we are unable to detect lines/columns, maximize
# the window.
if [ -z "${cols}" ]; then
	cols=999
	lines=999
	no_pass_thru=1 # force loading vimpager
fi

find_vim_dir() {
	vim -E -c 'call writefile([ $VIM ], "'${tmp}'/vimpager_vim_dir_'${$}'") | q' </dev/null
	vim_dir=$(head_n 1 "${tmp}/vimpager_vim_dir_${$}")

	rm -f "${tmp}/vimpager_vim_dir_${$}"
}

# this uses the heuristics described in :help startup
find_rc() {
	_cmd=$1
	_rc_file=$2

	[ -e "${HOME}/.${_rc_file}" ] && _rc="${HOME}/.${_rc_file}"
	[ -z "${_rc}" ] && [ -e "${HOME}/_${_rc_file}" ] && _rc="${HOME}/_${_rc_file}"

	if [ -n "${msys}" -o -n "${cygwin}" ]; then
		if command -v ${_cmd} 2>/dev/null | grep_q '^\/(cygdrive\/)?[a-z]\/'; then
			eval "win32_native_${cmd}=1"

			if [ -e "${HOME}/_${_rc_file}" ]; then
				_rc="${HOME}/_${_rc_file}"
			elif [ -z "${_rc}" ]; then
				[ -z "${vim_dir}" ] && find_vim_dir

				if [ -e "${vim_dir}/_${_rc_file}" ]; then
					_rc="${vim_dir}/_${_rc_file}"
				elif [ -e "${vim_dir}/.${_rc_file}" ]; then
					_rc="${vim_dir}/.${_rc_file}"
				fi
			fi
		fi
	fi

	echo "${_rc}"

	unset _cmd _rc_file _rc
}

# determine location of rc file

# first check for a user ~/.vimpagerrc
if [ -n "${VIMPAGER_RC}" ]; then
	vimrc="${VIMPAGER_RC}"
elif [ -f ~/.vimpagerrc ]; then
	vimrc=~/.vimpagerrc
elif [ -f ~/.vim/vimpagerrc ]; then
	vimrc=~/.vim/vimpagerrc
elif [ -f ~/_vimpagerrc ]; then
	vimrc=~/_vimpagerrc
fi

# then check if the user has a ~/.vimrc
[ -z "${vimrc}" ] && vimrc="$(find_rc vim vimrc)"

# then check for a global /etc/vimpagerrc and fall back to NORC
if [ -z "${vimrc}" ]; then
	if [ -f /usr/pkg/etc/vimpagerrc ]; then
		vimrc=/usr/pkg/etc/vimpagerrc
	elif [ -f /etc/vimpagerrc ]; then
		vimrc=/etc/vimpagerrc
	else
		vimrc=NORC
	fi
fi

# read settings
vim -u "${vimrc}" -E --cmd 'set nocp' -c '
	if !exists("vimpager_use_gvim")
		let vimpager_use_gvim=0
	endif

	if !exists("vimpager_disable_x11")
		let vimpager_disable_x11=0
	endif

	if !exists("vimpager_scrolloff")
		let vimpager_scrolloff=5
	endif

	if !exists("vimpager_passthrough")
		let vimpager_passthrough=0
	endif

	call writefile([ vimpager_use_gvim, vimpager_disable_x11, vimpager_scrolloff, vimpager_passthrough ], "'${tmp}'/vimpager_opts_'${$}'")

	quit
' </dev/null

[ "$(head_n 1 < "${tmp}/vimpager_opts_${$}")" = 1 ] && use_gvim=1

[ "$(head_n 2 < "${tmp}/vimpager_opts_${$}" | tail_n 1)" = 1 ] && disable_x11=1

scrolloff=$(head_n 3 < "${tmp}/vimpager_opts_${$}" | tail_n 1)

[ "$(head_n 4 < "${tmp}/vimpager_opts_${$}" | tail_n 1)" = 0 ] && no_pass_thru=1

if [ "${no_pass_thru}" = 0 ]; then
	# check if arithmetic expansion works, passthrough mode relies on it
	if [ x$(echo $((2+2)) 2>/dev/null) != x4 ]; then
		no_pass_thru=1
	fi
fi

rm -f "${tmp}/vimpager_opts_${$}"

if [ -n "${msys}" -o -n "${cygwin}" ]; then
	# msys/cygwin may be using a native vim, and if we're not in a real
	# console the native vim will not work, so we have to use gvim.

	if [ "x${TERM}" != "xdumb" -a "x${TERM}" != "xcygwin" -a "x${TERM}" != "x" ]; then
		if command -v vim 2>/dev/null | grep_q '^\/(cygdrive\/)?[a-z]\/'; then
			use_gvim=1
		fi
	fi
fi

if [ -n "${use_gvim}" ]; then
	# find the .gvimrc
	gvimrc="$(find_rc gvim gvimrc)"

	# determine if this is an ssh session and/or $DISPLAY is set
	if [ -n "${osx}" ]; then
		if [ -z "${SSH_CONNECTION}" ] && command -v mvim >/dev/null; then
			vim_cmd="mvim -R"
		else
			vim_cmd="vim -R"
		fi
	elif [ -n "${cygwin}" ]; then
		if command -v gvim >/dev/null; then
			# The Cygwin gvim uses X
			if [ -z "${win32_native_gvim}" ]; then
				if [ -z "${DISPLAY}" ]; then
					vim_cmd="vim -R"
				else
					vim_cmd='gvim -R'
				fi
			elif [ -z "${SSH_CONNECTION}" ]; then
				vim_cmd='gvim -R'
			else
				vim_cmd="vim -R"
			fi
		else
			vim_cmd="vim -R"
		fi
	elif [ -n "${msys}" ]; then
		if [ -z "${SSH_CONNECTION}" ] && command -v gvim >/dev/null; then
			vim_cmd='gvim -R'
		else
			vim_cmd="vim -R"
		fi
	elif [ -z "${DISPLAY}" ]; then
		vim_cmd='vim -R'
	else
		if command -v gvim >/dev/null; then
			vim_cmd='gvim -R'
		else
			vim_cmd="vim -R"
		fi
	fi
else
	vim_cmd='vim -R'
fi

rm -f gvim.exe.stackdump # for cygwin gvim, which can be part of vim

less_vim() {
	case ${vim_cmd} in
		vim*)
			if [ -n "${disable_x11}" ]; then
				vim_cmd="${vim_cmd} -X"
			fi

# 'unmap h' has to be the very last command in -c, to disable the less.vim help screen
			${vim_cmd} \
				-u "${vimrc}" \
				--cmd 'let vimpager=1 | runtime! macros/less.vim | set nocp' \
				-c "nnoremap <Up> 1<C-u>" \
				-c "set scrolloff=${scrolloff:-5} | set foldlevel=999 | set nonu | silent! set nornu |
				    nmap <ESC>u :nohlsearch<cr> | noremap q :<C-u>q<CR> | nnoremap <Down> 1<C-d>" \
				-c "unmap h" \
				"${@:--}"
			;;
		*) # gvim or mvim GUI
			[ -n "${gvimrc}" ] && colors=$(grep guifg ${gvimrc} 2>/dev/null | head_n 1)

			# Check if the user maximized the window in ~/_gvimrc on Win32, if
			# so restore on startup.
			if [ -n "${gvimrc}" -a \( -n "${cygwin}" -o -n "${msys}" \) ]; then
				simalt=$(grep simalt "${gvimrc}" 2>/dev/null | head_n 1)

				if [ -n "${simalt}" ]; then
					restore="simalt ~r"
				fi
			fi

			${vim_cmd} \
				-u "${vimrc}" \
				--cmd 'let vimpager=1 | runtime! macros/less.vim | set nocp' \
				-c "nnoremap <Up> 1<C-u>" \
				-c "set scrolloff=${scrolloff:-5} | set foldlevel=999 | set nonu | silent! set nornu |
				    nmap <ESC>u :nohlsearch<cr> | noremap q :<C-u>q<CR> | nnoremap <Down> 1<C-d> |
				    ${colors:-echo} | ${restore:-echo} | set lines=${lines} | set columns=${cols}" \
				-c "unmap h" \
				"${@:--}" &
			;;
	esac

	rm -f gvim.exe.stackdump # for cygwin gvim, which can be part of vim
}

awk_pstree_freebsd() {
	awk -v mypid=${1} '{
		cmd[$1]=$3
		ppid[$1]=$2
		arg[$1]=$4
	}
	END {
		while (mypid != 1 && cmd[mypid]) {
			if (cmd[mypid] == "/bin/sh" && arg[mypid] == "/usr/bin/man")
				ptree=mypid " /usr/bin/man\n" ptree
			else
				ptree=mypid " " cmd[mypid] "\n" ptree
			mypid=ppid[mypid]
		}
		print ptree
	}'
}

awk_pstree() {
	awk -v mypid=${1} '{
		cmd[$1]=$3
		ppid[$1]=$2
	}
	END {
		while (mypid != 1 && cmd[mypid]) {
			ptree=mypid " " cmd[mypid] "\n" ptree
			mypid=ppid[mypid]
		}
		print ptree
	}'
}

_do_ptree() {
	if [ -n "${solaris}" ]; then
		# Tested on Solaris 8 and 10
		ptree ${$}
	elif [ -n "${cygwin}" -o -n "${msys}" ]; then
		ps | awk '{ print $1 "\t" $2 "\t" $NF }' | awk_pstree ${$}
	elif [ -n "${openbsd}" ]; then
		ps awo pid=,ppid=,command= | awk_pstree ${$}
	elif [ -n "${freebsd}" ]; then
		ps aw -o pid= -o ppid= -o command= | awk_pstree_freebsd ${$}
	elif [ -n "${netbsd}" ]; then
		ps aw -o pid= -o ppid= -o command= | awk_pstree ${$}
	else
		# Tested on Linux and OS X
		ps awo pid=,ppid=,comm= | awk_pstree ${$}
	fi
}

# silence warnings about DY* variables on OSX
do_ptree() {
	_do_ptree "$@" 2>/dev/null
}

# Check if called from man, perldoc or pydoc
if do_ptree | grep_q '(^|\/)(man|perl(doc)?([0-9.]*)?|py(thon|doc|doc2))'; then
	extra_c='set ft=man'
fi

extra_cmd="let vimpager_ptree=[$(do_ptree | awk '{ print "\"" $2 "\"" }' | tr '\n' ',')] | call remove(vimpager_ptree, -1)"

mkdir -m 0700 "${tmp}/vimpager_${$}"

command -v perl > /dev/null && \
	perl -le 'exit($] >= 5.008001 ? 0 : 1)' && have_perl=1

# Normally, filter outputs ANSI-escape-code-free content to STDERR.
# When content is short, filter outputs content with ANSI escape codes to STDOUT.
filter() {
	OLDIFS="${IFS}"
	if [ -z "${no_pass_thru}" ]; then
		content=
		# ts: tab stop
		# nl: number of physycal lines
		# dh: display height
		# dw: display width
		ts=8; nl=0; dh=$((lines-2)); dw=$((cols))

		while [ ${nl} -le ${dh} ]; do
			IFS='
'
			read -r line || exec printf %s "${content}"
			content="${content}${line}
"
			nl=$((nl+1))

			# w: line width
			# t: number of consecutive tabs
			w=0; t=$((2+${#line}+1))
			IFS='	'
			for a in .	${line}.
			do
				if [ ${w} -gt 0 ]; then
					w=$((w+ts-w%ts))
					t=$((t-1))
				fi
				w=$((w+${#a}))
				t=$((t-${#a}))
			done
			w=$((w-8+t*ts-1))

			nl=$((nl+(w-1)/dw))
		done
	fi
	IFS="${OLDIFS}"
	if [ "x${have_perl}" != "x" ]; then
		( printf %s "${content}"; exec /bin/cat ) | \
			sed -e 's/\[[^m]*m//g' "${@}" | \
			perl -CIOL -pe 'no warnings "utf8"; s/.\010//g' >&2
	else
		( printf %s "${content}"; exec /bin/cat ) | \
			sed -e 's/\[[^m]*m//g' "${@}" | \
			sed -e 's/.//g' >&2
	fi
}

# Check for certain parameters to pass on to vim (or conceivably do something else)
# Currently only checks for "+G" as in less which will start reading the file
# at the end. Also supports just "+" as in vim.
# Couldn't use getopt or getopts as neither supports options prepended with +
while [ $# -gt 0 ] ; do
        case "$1" in
                "+G"|"+") vim_cmd="${vim_cmd} +"; shift ;;
                    "-c") shift; extra_c="$1"; shift ;;
                      -*) echo "bad option '$1'" ; exit 1 ;;
                       *) break ;;
         esac
done

filename=${@:-stdin}
filename=$(echo "${filename}" | tr '/' '_')
filename="${tmp}/vimpager_${$}/${filename}"

case $(echo "${@}" | tr 'A-Z' 'a-z') in
	*.gz)
		filename=$(echo ${filename} | sed -e 's/\.[Gg][Zz]$//')
		gunzip -c "${@}" | filter 2> "${filename}"
		;;
	*.bz2)
		filename=$(echo ${filename} | sed -e 's/\.[Bb][Zz]2$//')
		bunzip2 -c "${@}" | filter 2> "${filename}"
		;;
	*.xz)
		filename=$(echo ${filename} | sed -e 's/\.[Xx][Zz]$//')
		xzcat -c "${@}" | filter 2> "${filename}"
		;;
	*.z)
		filename=$(echo ${filename} | sed -e 's/\.[Zz]$//')
		uncompress -c "${@}" | filter 2> "${filename}"
		;;
	*)
		cat "${@:--}" | filter 2> "${filename}"
		;;
esac

# dumb man detection when the pstree heuristic fails
if head_n 12 "${filename}" | grep_q '^NAME$'; then
	extra_c='set ft=man'
fi

# if file is zero length, or one blank line (cygwin) exit immediately
if [ \( ! -s "${filename}" \) \
	-o \( \( "$(cat "${filename}")" = "" \) \
	-a \( "$(wc -l "${filename}" | awk '{print $1}')" = "1" \) \) ]; then

	rm -rf "${tmp}/vimpager_${$}"
	exit
fi

# On cygwin it might be the win32 gvim, but windows paths work for cygwin
# vim just fine as well.
if [ -n "${cygwin}" ]; then
	filename=$(cygpath -w "${filename}")
fi

less_vim -c "${extra_c:-echo}" --cmd "${extra_cmd:-echo}" "${filename}" </dev/tty

# Give gvim/mvim time to open the file.
# On Win32 we must also wait for the process to exit before the file can
# be deleted.
(
	while [ -d "${tmp}" ]; do
		sleep 10
		rm -rf "${tmp}" 2>/dev/null
		rm -f gvim.exe.stackdump 2>/dev/null
	done
) &

# Copyright (c) 2014, Rafael Kitover <rkitover@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# CONTRIBUTORS:
#
# Rafael Kitover
# Antonio Ospite
# Jean-Marie Gaillourdet
# Perry Hargrave
# Koen Smits
# Ivan S. Freitas <ivansichfreitas@gmail.com>
# Wout Mertens (Solaris compatibility, less processes)
# Jacobo de Vera (add -X option for faster startup)
# Damien Pollet <damien.pollet@gmail.com>
# Peter Fern <github@obfusc8.org>
# Wei Dai <x@wei23.net>
# Iftekharul Haque <iftekharul.haque@gmail.com>
# Anselm Strauss <amsibamsi@gmail.com>
# Anisse Astier <anisse@astier.eu>
# Simon Olofsson <simon@olofsson.de>
# lickel: Adam Lickel <adam@lickel.com>
# eworm-de: Christian Hesse <mail@eworm.de>
# krijesta: Chris Chambers <krijesta@google.com>
# vincer: vince rosso <vince@locationlabs.com>
# justinkb: Paul Mulders <justinkb@gmail.com>
# nonakap: NONAKA Kimihiro <nonakap@gmail.com>
# dfechner: Dustin Fechner <fechnedu@gmail.com>
# lucc: Lucas Hoffmann <l-m-h@web.de>
# aroig: Abdo Roig-Maranges <abdo.roig@gmail.com>
# mortonfox: Morton Fox <github@mortonfox.otherinbox.com>
# mapeiqi88: <mapeiqi88@gmail.com>
# snordhausen: Stefan Nordhausen <stefan.nordhausen@axiros.com>

# vim:noet ts=8 sts=8 sw=8 tw=0:
