# -*- shell-script -*-
#######################################################################
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  
#
#######################################################################
#
# bashrun default configuration and bash setup.
#
# This is the rcfile for the bash session used by bashrun. It
# initializes the default configuration, merges in the user's config
# file and sets up the bash session for bashrun.
#
# DO NOT EDIT THIS FILE, EDIT ~/.config/bashrun/rc INSTEAD.
#
#######################################################################
#
# Basic operation
#
# 1. Readline macros rewrite the commandline to 
#
#      'bashrun-set-mode <mode>; <commandline>'
#
#    and call readline accept-line.
#
# 2. the bashrun-set-mode function sets the MODE variable and enables
#    the debug trap via 'trap hook DEBUG'
#
# 3. The debug trap is called before each command on the commandline
#    is executed. It sets COMMAND to the BASH_COMMAND about to be
#    executed, and calls run to handle it, then returns 1 to
#    prevent normal execution by bash. The trap builtin is explicitly
#    excluded from this behaviour.
#
# 4. For each of the following commands, the run function
#    
#    a) Checks if the command is executable. If not, apply_handlers
#       tries to match and rewrite the command according to the user
#       configured HANDLERS array (unless for modes google-search,
#       dict-lookup, copy, copy-and-paste)
#    
#    b) the run function then modifies COMMAND according to the
#       requested mode and the user's preferences. It applies the
#       TERMINAL_RULES unless the (run|hold|page)-in-terminal modes
#       are explicitly used.
#
#    c) if the first word of the resulting commandline is executable,
#       it is launched in a subshell detached from this
#       session. (execute)
#
#    d) else, if GOOGLE_FALLBACK is "yes", the contents of COMMANDLINE
#       are googled.
#
# 5. The PROMPT_COMMAND disables the DEBUG trap, unmaps the window and
#    cleans up the history file, removing the command added by
#    readline macros.
#
# The special mode "pass" allows commands to be executed as usual. The
# <pass> action enables this mode, so that a command like
# 'GOOGLE_FALLBACK="no"<pass>' can be used to set config variables at
# runtime.
#
########################################################################
#
# Internal keybindings
#
# To allow (almost) arbitrary redefintion of keybindings by the user,
# all line editing commands that bashrun internally uses for
# commandline rewriting have been bound in the reserved ranges
# C-x[n][...] and \20[n] :
#
#### line editing
#
# C-x00 : beginning-of-line
# C-x01 : end-of-line
# C-x02 : kill-line
# C-x03 : yank
# C-x04 : vi-insertion-mode
#
#### command execution 
#
# C-x10 : accept-line
# C-x11 : pass
# C-x12 : su-accept-line
#
#### bashrun specific functions
#
# C-x20 : bashrun-bindings
# C-x21 : bashrun-manual
# C-x22 : bashrun-handlers
# C-x23 : bashrun-bookmarks
# C-x24 : bashrun-about
#
#### terminal control
#
# \201  : resize-up
# \202  : resize-down
# \203  : resize-left
# \204  : resize-right
# \205  : toggle-size
# \206  : toggle-debug
# \207  : unmap-window
#
####
#
# This ensures that the user may rebind default readline keybindings
# like C-a without disabling readline macros used by bashrun
# internally.
#
#######################################################################

CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/bashrun
DATA_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/bashrun
CACHE_DIR=${XDG_CACHE_HOME:-$HOME/.cache}/bashrun

CONFIG=$CONFIG_DIR/rc
KEYCONF=$CONFIG_DIR/keys

PREFIX=$(which bashrun | sed "s:/bin/bashrun::")
BASHRC=$PREFIX/share/bashrun/bashrc

# default configuration ----------------------------------------------

XTERM=xterm
XTERM_OPTIONS=
KEEP_OPEN=no
SMALL_COLUMNS=40
SMALL_LINES=1
LARGE_COLUMNS=40
LARGE_LINES=8
BGCOLOR=black
FGCOLOR=grey
PS1=">"
HISTFILE=$DATA_DIR/history
HISTCONTROL=ignoredups:erasedups
COMPLETION_TYPE=menu-complete
ALTERNATIVE_COMPLETION_TYPE=complete
COMPLETION_THRESHOLD=$LARGE_LINES

USE_EXTENDED_BASH_COMPLETION=no
EXTENDED_BASH_COMPLETION_PATH=/etc/profile.d/bash_completion.sh
TERMINAL_RULES=(
    "RUN:"
    "PAGE:"
    "HOLD:"
)
PAGER=${PAGER:-less}
DICT_CLIENT="dict --pager $PAGER"
DIRHANDLER='bashrun_show_message Configure DIRHANDLER in $CONFIG to handle directories --'
POST_MAP_COMMAND=""
BROWSER=${BROWSER:-firefox}
GOOGLE_FALLBACK=no

HANDLERS=(
    '^https?'   "$BROWSER"
    '^www'      "$BROWSER"
    '^gg:(.+)'  "$BROWSER 'http://www.google.com/search?q=\$1'"
    '^ggl:(.+)' "$BROWSER 'http://www.google.com/search?q=\$1&btnI=I%27m+Feeling+Lucky'"
    '^fm:(.+)'  "$BROWSER 'http://freshmeat.net/search/?q=\$1&section=projects'"
    '^sf:(.+)'  "$BROWSER 'https://sourceforge.net/projects/\$1/'"
    '^sfw:(.+)' "$BROWSER 'http://\$1.sourceforge.net/'"
    '^ftp:(.+)' "xterm -e 'ncftp \$1'"
    '^\?\?(.+)' "xterm -e 'man \$1'"
    '^\?(.+)'   "xterm -e '\$1 --help | $PAGER'"
    )  

BOOKMARKS=(
    www.google.com
    www.archlinux.org
    www.slashdot.org
    www.linux.com
    )

LOGFILE=/dev/null

# process user configuration -----------------------------------------

function defined() {
    [ ${!1-X} == ${!1-Y} ]
}

. $CONFIG

RUN=()
HOLD=()
PAGE=()

# add dict to RUN rule
TERMINAL_RULES[0]="${TERMINAL_RULES[0]} dict"

# setup terminal rules
for (( r=0; r<${#TERMINAL_RULES[@]}; r++ )); do
    
    RULE=${TERMINAL_RULES[$r]}

    if [[ "$RULE" =~ ^([A-Z]+): ]]; then	
	
	TYPE=${BASH_REMATCH[1]}
	RULE=${RULE/$TYPE/}
	RULE=${RULE/:/}

	for PROG in $RULE; do
	    case $TYPE in
		RUN)
		    RUN[${#RUN[@]}]=$PROG
		    ;;
		HOLD)
		    HOLD[${#HOLD[@]}]=$PROG
		    ;;
		PAGE)
		    PAGE[${#PAGE[@]}]=$PROG
		    ;;
	    esac
	done
    fi
done

# add directory handler
HANDLERS[${#HANDLERS[@]}]='.+'
HANDLERS[${#HANDLERS[@]}]="dr:$DIRHANDLER"
HANDLERS[${#HANDLERS[@]}]='(.+)'
HANDLERS[${#HANDLERS[@]}]='d!r:bashrun_show_error Directoy $1 is not readable.' 

# use bash_completion.sh if requested
if [ $USE_EXTENDED_BASH_COMPLETION == "yes" ]; then
    if [ -f $EXTENDED_BASH_COMPLETION_PATH ]; then
	source $EXTENDED_BASH_COMPLETION_PATH
    fi
fi

# add bookmarks to command completion list
for BOOKMARK in ${BOOKMARKS[@]}; do
    eval "function $BOOKMARK { return; }"
done

# fix HISTIGNORE for exits and --debug toggling
HISTIGNORE="$HISTIGNORE:unset HISTFILE*"
HISTIGNORE="$HISTIGNORE:trap DEBUG;toggle_debug"

# support old LONG_COLUMNS, LONG_LINES
defined LONG_COLUMNS && LARGE_COLUMNS=$LONG_COLUMNS
defined LONG_LINES && LARGE_LINES=$LONG_LINES

# bash setup ---------------------------------------------------------

PS2="!Please press C-c>"
PROMPT_COMMAND='trap DEBUG; shopt -s extdebug; bashrun_unmap_window'

# readline setup
bind 'set horizontal-scroll-mode on'

# disable sending of eof (C-d)
if [[ BASHRUN_XDOTOOL -eq 1 ]]; then
    stty eof '^¬'
fi

# enable checkwinsize
shopt -s checkwinsize

# extended debugging features
# shopt -s extdebug

# globals ------------------------------------------------------------

MODE=""
COMMANDLINE=""
COMMAND=""
EXECMODE="&"
BINARY=""
APPLY_HANDLERS=1
CURRENT_COMPLETION="undefined"

# colors
W="\033[1;37m"
R="\033[1;31m"
G="\033[1;32m"
B="\033[1;34m"
Z="\033[1;36m"
M="\033[1;35m"
Y="\033[1;33m"
N="\033[0m"

# sizes
TERM_SIZE="small" # || "long"
WIDTH=$COLUMNS
HEIGHT=$LINES

# functions ----------------------------------------------------------

function bashrun-set-mode {

    # set mode and trap DEBUG
    MODE=$1
    APPLY_HANDLERS=${2:-1}

    separator
    debug "${B}mode: $M$MODE$N"
    debug "${Z}trap hook DEBUG$N"

    cleanup_history

    trap hook DEBUG 
}

function hook {

    # the COMMANDLINE before shell expansions
    get_commandline

    # the command about to be executed,
    # already expanded by the shell
    COMMAND="$BASH_COMMAND"

    # assume execmode background
    EXECMODE="&"

    # remove quotes (;|&)
    unquote_command
    
    # pass through trap builtin
    if [[ "$COMMAND" =~ ^trap ]]; then
	debug "$Z$COMMAND$N"
	separator
	return 0
    fi
    
    # passthrough commands (M-w)
    if [ "$MODE" == "pass" ]; then
	debug "$M$COMMAND$N" "info"
	return 0
    fi

    run
    return 1
}
		
function run {

    get_binary
    
    debug "${B}commandline:$N $COMMANDLINE" "info"
    debug "${B}command:$N $COMMAND$B binary: $N$BINARY" "info"

    if ! is_executable?; then	
	apply_handlers
    fi

    debug "${B}handling mode${Z} $MODE$N"

    case $MODE in 
	
	run)
	    apply_terminal_rules
	    ;;
	
	su-run)	    
	    apply_terminal_rules
	    su_command
	    ;;

	run-in-terminal)
	    add_terminal
	    ;;

	su-run-in-terminal)
	    add_terminal
	    su_command
	    ;;
	
	hold-in-terminal)
	    COMMAND="$COMMAND;read -n1"
	    add_terminal
	    ;;

	su-hold-in-terminal)
	    COMMAND="$COMMAND;read -n1"
	    add_terminal
	    su_command
	    ;;

	page-in-terminal)
	    COMMAND="($COMMAND) | $PAGER"
	    add_terminal
	    ;;

	su-page-in-terminal)
	    COMMAND="($COMMAND) | $PAGER"
	    add_terminal
	    su_command
	    ;;

	show-manual)
	    COMMAND="man $BINARY"
	    add_terminal
	    ;;
    
	show-info)
	    COMMAND="info $BINARY"
	    add_terminal
	    ;;
    
	show-help-builtin)
	    if is_builtin?; then
		COMMAND="help $BINARY | $PAGER"
	    else
		debug "$BINARY${B}: not a builtin, showing manual$N" "fail"
		COMMAND="man $BINARY"
	    fi	    
	    add_terminal
	    ;;
	copy)
	    COMMAND="($COMMAND) | xclip"
	    ;;
	
    
        copy-and-paste)
	    COMMAND="($COMMAND) | xclip -i; bashrun --hide; bashrun --wait; xdotool click 2"
	    ;;

	filter)
	    COMMAND="xclip -o | ($COMMAND) | xclip -i" 
	    ;;

	filter-and-paste)
	    COMMAND="xclip -o | ($COMMAND) | xclip -i; bashrun --hide; bashrun --wait; xdotool click 2" 
	    ;;

	dict-lookup)
	    COMMAND="$DICT_CLIENT $COMMANDLINE"
	    if ! is_executable?; then
		bashrun_show_error "Dictionary client '$DICT_CLIENT' not found."		
		return 1
	    fi
	    apply_terminal_rules
	    ;;
    
	browse)
    	    COMMAND="$BROWSER '$COMMANDLINE'"
	    ;;

        google-search)
    	    COMMAND="$BROWSER 'http://www.google.com/search?q=$COMMANDLINE'"
	    ;;
    
	bindings)
	    pp_bindings
	    COMMAND="$PAGER $CACHE_DIR/keybindings"
	    add_terminal
	    ;;

	manual)
	    COMMAND="man bashrun"
	    add_terminal
	    ;;

        handlers)
	    COMMAND="$PAGER $CACHE_DIR/handlers"
	    add_terminal
	    ;;

	bookmarks)
	    COMMAND="$PAGER $CACHE_DIR/bookmarks"
	    add_terminal
	    ;;
	about)
	    COMMAND="bashrun --version | $PAGER"
	    add_terminal
	    ;;
	*)
	    COMMAND="bashrun_show_error Mode '$MODE' not implemented."
	    ;;
    esac

    # execute or fall back
    if is_executable?; then
	execute
    else
	debug "${B}not executable:$N $COMMAND" "fail"
	if [ "$GOOGLE_FALLBACK" == "yes" ]; then
	    debug "${G}(GOOGLE_FALLBACK)$N"
	    COMMAND="$BROWSER 'http://www.google.com/search?q=$COMMAND'"
	    execute
	fi
    fi
}

function execute {

    local logfile="$LOGFILE"
    
    # revert logfile to /dev/null if LOGFILE was empty
    [[ "$logfile" == "" ]] && logfile="/dev/null"
 
    # create logfile unless it exists
    if [[ ! -e "$logfile" ]]; then
	debug "${B}creating logfile $N$logfile"
	touch "$logfile"
    fi

    # revert logfile to /dev/null unless it's writable
    if [[ ! -w "$logfile" ]]; then
	debug "${B}logfile $N$logfile ${B}is not writeable$N" "fail"
	logfile="/dev/null"
    fi
    
    debug "${B}redirecting stdout/stderr to $N$logfile" 
    debug "$COMMAND"

    # execute command
    if [ "$EXECMODE" == "&" ]; then
	/bin/sh -c "$COMMAND >> $logfile 2>&1 &" > /dev/null 2>&1
    else
	# this can only be a su command (so far)
	/bin/sh -c "$COMMAND"
	if [ "$?" != "0" ]; then
	    bashrun_show_error "su: incorrect password"
	fi
    fi
}

function su_command {
    
    # run the command itself in the background
    if [[ ! "$COMMAND" =~ \&\ *$ ]]; then
	COMMAND="su root -c '$COMMAND > /dev/null 2>&1 &'"
                # FIXME: quotes?
		# one level of double quotes works, 
		# but not single quotes
    fi
    # run the su part (passwort entry) in foreground 
    EXECMODE="" 
    debug "$COMMAND"
}

function apply_handlers {
    # applies matching HANDLERS to COMMAND

    if [[ ! APPLY_HANDLERS -eq 1 ]]; then
	return
    fi

    debug "trying to match handlers..." "info"
    
    for ((i=0; i<${#HANDLERS[*]}; i+=2)); do
	local BASHRUN_APPEND=1
	local PATTERN=${HANDLERS[$i]}
	local HANDLER=${HANDLERS[$i+1]}
	if [[ "$COMMAND" =~ $PATTERN ]]; then	    

	    debug "$COMMAND ${B}matches$N $PATTERN" "info"

	    # check for file test prefix
	    if [[ "$HANDLER" =~ ^([abcdefghkprstuwxOGLSN!]+): ]]; then
		local TESTS=${BASH_REMATCH[1]}
		HANDLER=${HANDLER/$TESTS:/}

		# perform tests
		local TEST=""
		local SUCCESS=0
		for(( k=0; k<${#TESTS}; k++ )); do
		    local CHAR=${TESTS:$k:1}
		    if [ "$CHAR" == "!" ]; then
			TEST="!"
			continue
		    else
			TEST="$TEST -$CHAR"
			if eval "test $TEST $BINARY"; then
			    debug "test $TEST $BINARY ${B}-> ${G}SUCCESS$N"
			    SUCCESS=1
			else
			    debug "test $TEST $BINARY${B} -> ${R}FAIL$N" "fail"
			    SUCCESS=0
			    break
			fi			
			TEST=""
		    fi
		done
		if [[ ! SUCCESS -eq 1 ]]; then
		    debug "${B} -> ${R}not handled$N" "fail"
		    continue
		fi
	    fi

	    # rewrite handler if subpatterns matched
	    local MESSAGE="$HANDLER"
	    [[ "$COMMAND" =~ $PATTERN ]]
	    for (( i=1; i<${#BASH_REMATCH[*]}; i++ )); do

		MATCH=${BASH_REMATCH[$i]}
		debug "${B}subexpression match $Y\$$i $G$MATCH$N"
		HANDLER=${HANDLER//\$$i/$MATCH}

		MESSAGE=${MESSAGE//\$$i/$G$MATCH$N}
		debug "${B}-> $N$MESSAGE"

		export BASHRUN_APPEND=0
	    done

	    # fail if unmatched $n's remain in handler string
	    if [[ $HANDLER =~ (\$[0-9]) ]]; then
		local MISSING=${BASH_REMATCH[1]}
		debug "${B}handler incomplete: ${R}no content$B for $N$MISSING" "fail"
		debug "${B}-> not executing, informing user" "fail"
		COMMAND="bashrun_show_error No content for \\$MISSING in $PATTERN handler."
		return
	    fi

	    if [[ BASHRUN_APPEND -eq 1 ]]; then
		COMMAND="$HANDLER $COMMAND"
	    else
		COMMAND="$HANDLER"
	    fi

	    debug "${B} -> ${G}handled$N"
	    return
	fi
    done
}

function add_terminal {

    # wrap COMMAND in terminal invocation

    if [[ "$BINARY" == "$XTERM" ]]; then
	debug "already in terminal" "info"
	return
    fi

    # add XTERM_OPTIONS
    local XTERM_COMMAND="$XTERM $XTERM_OPTIONS"

    # COMMAND goes into -e "" -> escape double quotes
    COMMAND=${COMMAND//\"/\\\"}

    # create xterm command string 
    if [[ "$XTERM" =~ xterm ]]; then
	XTERM_COMMAND="$XTERM_COMMAND -bg $BGCOLOR -fg $FGCOLOR -e \"\$1\""

    elif [[ "$XTERM" =~ aterm ]]; then
	XTERM_COMMAND="$XTERM_COMMAND -background $BGCOLOR -foreground $FGCOLOR -e /bin/bash -i -c \"\$1\""
    
    elif [[ "$XTERM" =~ mlterm ]]; then
	XTERM_COMMAND="$XTERM_COMMAND -b $BGCOLOR -f $FGCOLOR -e /bin/bash -i -c \"\$1\""

    elif [[ "$XTERM" =~ rxvt ]]; then
	XTERM_COMMAND="$XTERM_COMMAND -background $BGCOLOR -foreground $FGCOLOR -e sh -c \"\$1\""
    fi
    
    # insert COMMAND
    COMMAND=${XTERM_COMMAND/\$1/$COMMAND}
    debug "${B} terminal added:$N $COMMAND"
}

function apply_terminal_rules {
    
    # add terminal if BINARY is included in
    # ALWAYS_(HOLD|PAGE)_IN_TERM

    get_binary
    
    if [[ "$COMMAND" =~ ^$XTERM ]]; then
	debug "already in terminal" "fail"
	return
    fi
    
    local PROG=""
    
    # always run
    for PROG in ${RUN[@]}; do
	if [ "$PROG" == "$BINARY" ]; then
	    debug "BINARY $B(RUN)$N" "info"
	    add_terminal
	    return
	fi
    done
    
    # always hold
    for PROG in ${HOLD[@]}; do
	if [ "$PROG" == "$BINARY" ]; then
	    debug "$BINARY $B(HOLD)$N"
	    COMMAND="$COMMAND;read -n1"
	    add_terminal
	    return
	fi
    done
    
    # always page
    for PROG in ${PAGE[@]}; do
	if [ "$PROG" == "$BINARY" ]; then
	    debug "$BINARY $B(PAGE)$N"
	    COMMAND="($COMMAND) | $PAGER"
	    add_terminal
	    return
	fi
    done
}

function get_commandline {
    
    # retrieve the verbatim commandline from history, before shell
    # expansions (assumes to be called after cleanup_history)

    # unset HISTTIMEFORMAT
    local histtimeformat=$HISTTIMEFORMAT
    unset HISTTIMEFORMAT

    [[ $(history 1) =~ \ +[0-9]+?\ +(.+?) ]]
    COMMANDLINE=${BASH_REMATCH[1]}
    
    # reset HISTTIMEFORMAT
    if [[ "$histtimeformat" != "" ]]; then
	HISTTIMEFORMAT=$histtimeformat
    fi
}

function get_binary {

    # updates BINARY to contain the first word of the current command.

    BINARY="$COMMAND"
    BINARY=${BINARY//\\ /<qspace>}
    if [[ "$BINARY" =~ ([^\ \&\;\|]+) ]]; then
	BINARY=${BASH_REMATCH[1]}
    fi
    BINARY=${BINARY//<qspace>/\\ }
    BINARY=$(echo $BINARY | sed -r "s/^ +//;s/ +$//")
    BINARY=$(echo $BINARY | sed -r "s/^\\(+//;s/\\)+$//")

}

function is_executable? {
    
    # check if BINARY is executable

    get_binary

    local TYPE=`type -t $BINARY`

    if [[ "$TYPE" =~ (alias|keyword|builtin) ]]; then
	debug "$BINARY$B -> $N$TYPE${B} -> yes$N"
	return 0
    fi

    if [[ "$TYPE" == "function" ]]; then
	if is_bookmark?; then
	    debug "$BINARY$B -> ${N}bookmark${B} -> no$N" "fail"
	    return 1
	else
	    debug "$BINARY$B -> $N$TYPE${B} -> yes$N"
	    return 0
	fi
    fi

    if [[ "$TYPE" == "file" ]]; then
	if [[ `which $BINARY 2> /dev/null` ]]; then
	    debug "${B}which:$N `which $BINARY`${B} -> yes$N"
	    return 0
	fi
	if [[ -x $BINARY ]]; then
	    debug "${B}+x ${N}$BINARY${B} -> yes$N"
	    return 0
	fi
    fi

    debug "$BINARY$B -> ${R}no$N" "fail"
    return 1
}

function is_builtin? {

    # check if BINARY is a shell builtin command

    get_binary

    local RESULT=`type $BINARY 2>&1 | head -n1`

    if [[ "$RESULT" =~ builtin ]]; then
	debug "$BINARY$B: ${G}yes$N"
	return 0
    fi
    debug "$BINARY$B: ${R}no$N" "fail"
    return 1
}

function is_bookmark? {
    local bookmark
    for bookmark in ${BOOKMARKS[@]}; do
	[[ "$BINARY" == "$bookmark" ]] && return 0
    done;
    return 1
}

function unquote_command {

    # unquote quoted &;|
    COMMAND=${COMMAND//\\&/&}
    COMMAND=${COMMAND//\\|/|}
    COMMAND=${COMMAND// \\; /;}
    COMMAND=${COMMAND//&&/ && }
    COMMAND=${COMMAND//||/ || }
}

function cleanup_history {

    local LINE=$(history 1)
    local ORIG_LINE=""
    local POS=""

    # match lines modified by bashrun only
    if [[ "$LINE" =~ \ +([0-9]+)\ +.*?(bashrun-set-mode[^\;]+\;(.*?)) ]]; then

	POS=${BASH_REMATCH[1]}
	ORIG_LINE=${BASH_REMATCH[2]}
	LINE=${BASH_REMATCH[3]}

	debug "${B}POS: $N$POS$B: '$N$ORIG_LINE$B' -> '$N$LINE$B'$N"

	# trim whitespace at end of line
	LINE=$(echo "$LINE" | sed -r 's/ +$//')
	
	if [[ "$LINE" == "" || "$LINE" == ":" ]]; then
	    # delete empty last line
	    history -d $POS
	    debug "${B}deleted $N$POS$B == '$N$LINE$B'$N'"
	else
	    # replace last line 
	    history -s "$LINE"
	    debug "${B}replaced $B'$N$ORIG_LINE$B' -> '$N$LINE$B'$N"
	fi
	history -w
	history -c
	history -r
    fi
    [[ BASHRUN_XDOTOOL -eq 0 ]] && unset HISTFILE
}

function get_editing_mode {
    
    if shopt -q -o emacs; then
	BASHRUN_EDITING_MODE=emacs

    elif shopt -q -o vi; then
	BASHRUN_EDITING_MODE=vi
    else
	BASHRUN_EDITING_MODE=unknown
    fi
    export BASHRUN_EDITING_MODE
}

function debug {
    
    # $1: debug message
    # $2: level: "success" (green) (default)
    #            "fail" (red) 
    #            "info" (blue)

    local COLOR="$G"

    if [ "$2" == "success" ]; then
	COLOR="$G"
    fi
    
    if [ "$2" == "fail" ]; then
	COLOR="$R"
    fi

    if [ "$2" == "info" ]; then
	COLOR="$G"
    fi

    if [[ BASHRUN_DEBUG -eq 1 ]]; then
	local FUNC=${FUNCNAME[1]}
	local FROM=${FUNCNAME[2]}
	FUNC=${FUNC//}
	FROM=${FROM//}
	FROM=${FROM:-main}

	echo -en "$COLOR$FUNC$N "
	if [[ "$FUNC" == "bind_keys" ]]; then
	    echo -n "$1"
	else
	    echo -ne "$1"
	fi
	echo -e "$N"
    fi
}

function toggle_debug {
    
    # toggle debug mode

    if [[ BASHRUN_DEBUG -eq 0 ]]; then
	BASHRUN_DEBUG=1
	echo -ne "\e[8;24;80t"; 
	clear;
	echo -e "${B}DEBUG ON$N"
    else
	BASHRUN_DEBUG=0
	echo -ne "\e[8;${SMALL_LINES};${SMALL_COLUMNS}t"; 
	echo -e "${B}DEBUG OFF$N"
    fi
}

function separator {
    if [[ BASHRUN_DEBUG -eq 1 ]]; then
	echo -ne "$B"
	for (( i=1; i<=$COLUMNS; i++ )); do
	    echo -n "-"
	done
	echo -ne "$N"
	echo
    fi
}

function pp_handlers {

    # pretty print handlers (F3)

    echo -n "" > $CACHE_DIR/handlers
    for (( i=0; i<${#HANDLERS[@]}; i+=2 )); do
	PATTERN=${HANDLERS[$i]}
	HANDLER=${HANDLERS[$i+1]}
	echo -e "$Y$PATTERN $N-> $B$HANDLER$N" >> $CACHE_DIR/handlers
    done
}

function pp_bookmarks {

    # pretty print bookmarks (not used)

    echo -n "" > $CACHE_DIR/bookmarks
    for BOOKMARK in ${BOOKMARKS[@]}; do
	echo -e "$BOOKMARK" >> $CACHE_DIR/bookmarks
    done
}

function toggle_size {
  
    declare -i columns
    declare -i lines

    if [[ "$TERM_SIZE" == "small" ]]; then
	TERM_SIZE="large"
	columns=$LARGE_COLUMNS
	lines=$LARGE_LINES
    else
	TERM_SIZE="small"
	columns=$SMALL_COLUMNS
	lines=$SMALL_LINES
    fi
    resize $columns $lines
}

function resize {

    # resize [up|down|left|right] or
    # resize columns lines

    local dir=$1
    local columns=$1
    local lines=$2

    # set new WIDTH, HEIGHT
    case "$dir" in
	up)
	    let HEIGHT--
	    ;;
	down)
	    let HEIGHT++
	    ;;
	left)
	    let WIDTH--
	    ;;
	right)
	    let WIDTH++
	    ;;
	*)
	    WIDTH=$columns
	    HEIGHT=$lines
	    ;;
    esac
    
    # keep least 1x1
    [[ $HEIGHT -eq 0 ]] && HEIGHT=1
    [[ $WIDTH -eq 0 ]] && WIDTH=1

    # update TERM_SIZE vars
    case "$TERM_SIZE" in
	small)
	    SMALL_COLUMNS=$WIDTH
	    SMALL_LINES=$HEIGHT
	    debug "${B}update $N${TERM_SIZE}-mode $SMALL_COLUMNS $SMALL_LINES"
	    ;;
	large)
	    LARGE_COLUMNS=$WIDTH
	    LARGE_LINES=$HEIGHT
	    debug "${B}update $N${TERM_SIZE}-mode $LARGE_COLUMNS $LARGE_LINES"
	    ;;
    esac

    debug "${B}to$N $WIDTH $HEIGHT"

    # resize
    echo -ne "\e[8;${HEIGHT};${WIDTH}t"; 
    [[ $BASHRUN_DEBUG -eq 0 ]] && clear

    apply_completion_threshold
}

function apply_completion_threshold {

    [[ $COMPLETION_THRESHOLD -lt 1 ]] && return

    declare -i t=$COMPLETION_THRESHOLD
    let t--

    if [[ $HEIGHT -gt $t ]]; then
	bind 'set horizontal-scroll-mode off'
	set_completion_type $ALTERNATIVE_COMPLETION_TYPE
    else
	bind 'set horizontal-scroll-mode on'
	set_completion_type $COMPLETION_TYPE
    fi
}

function set_completion_type {

    local ctype=$1

    if [[ "$CURRENT_COMPLETION" == "$ctype" ]]; then
	return
    fi

    # reset readline settings to defaults
    bind 'set page-completions on'
    bind 'set print-completions-horizontally off'
    bind 'set completion-query-items 100'

    # set completion type
    case "$ctype" in
	"complete")
	    bind '"\t":complete'
	    ;;
	menu-complete)
	    bind '"\t":menu-complete'
	    ;;
	quiet-complete)
	    bind '"\t":complete'
	    bind 'set page-completions off'
	    bind 'set print-completions-horizontally on'
	    bind 'set completion-query-items -1'
	    ;;
	*)
	    debug "${B} unknown completion type: $n$ctype" "fail"
	    ;;
    esac
    CURRENT_COMPLETION=$ctype
    debug "$ctype"
}
set_completion_type $COMPLETION_TYPE

function keyseq2keyname {
    local keyname=$1
    local enter="Ret"
    keyname=${keyname/\\e/M-}
    keyname=${keyname/\\C-/C-}
    keyname=${keyname/C-m/$enter}
    keyname=${keyname/C-j/$enter}
    if [[ "$keyname" =~ [a-zA-z]$enter ]]; then
	keyname=${keyname/$enter/-$enter}
    fi
    echo "$keyname"
}

# array of command names, descriptions, bindings (emacs, vi-insert,
# vi-command), and xdotool used in pp_bindings, filled by bindkeys
KEYDESC=(
    'bashrun-bindings' 'Show current keybindings' '' '' '' '0'
    'bashrun-manual'   'Show bashrun manual' '' '' '' '0'
    'bashrun-handlers' 'Show handlers' '' '' '' '0'
    'bashrun-debug'    'Toggle debug mode' '' '' '' '0'
    'abort'            'Abort (terminate shell or unmap window)' '' '' '' '0'
    'quit'             'Quit (terminate shell)' '' '' '' '0'
    'run'              'Run command' '' '' '' '0'
    'term-run'         'Run command in terminal' '' '' '' '0'
    'term-page'        'Run command in terminal and page' '' '' '' '0'
    'term-hold'        'Run command in terminal and hold' '' '' '' '0'
    'su-run'           'Run command as root' '' '' '' '0'
    'su-term-run'      'Run command as root in terminal' '' '' '' '0'
    'su-term-page'     'Run command as root in terminal and page' '' '' '' '0'
    'su-term-hold'     'Run command as root in terminal and hold' '' '' '' '0'
    'pass'             'Run command in bashrun shell' '' '' '' '0'
    'show-manual'      'Show manual page for command' '' '' '' '0'
    'show-info'        'Show info page for command' '' '' '' '0'
    'show-help'        'Show help for bash builtin' '' '' '' '0'
    'browse'           "Browse url using $BROWSER" '' '' '' '0'
    'google-search'    "Google words using $BROWSER" '' '' '' '0'
    'dict-lookup'      "Lookup words using $DICT_CLIENT" '' '' '' '0'
    'copy'             'Copy command output to clipboard' '' '' '' '0'
    'copy-paste'       'Copy output and paste into window under mouse' '' '' '' '1'
    'filter'           'Run command as filter on clipboard' '' '' '' '0'
    'filter-paste'     'Run command as filter on clipboard and paste' '' '' '' '1'
    'toggle-size'      'Toggle long and standard size (xterm, *rxvt)' '' '' '' '0'
    'resize-up'        'Resize terminal up' '' '' '' '0'
    'resize-down'      'Resize terminal down' '' '' '' '0'
    'resize-left'      'Resize terminal left' '' '' '' '0'
    'resize-right'     'Resize terminal right' '' '' '' '0'
    )

function update_keydesc {
    local command=$1
    local keyname=$2
    local keymap=$3
    local i=0

    for ((i=0; i<${#KEYDESC[*]}; i+=6)); do
	if [ "${KEYDESC[$i]}" == "$command" ]; then
	    case $keymap in
		emacs)
		    KEYDESC[$i+2]="$keyname"
		    ;;
		vi-insert)
		    KEYDESC[$i+3]="$keyname"
		    ;;
		vi-command)
		    KEYDESC[$i+4]="$keyname"
		    ;;
	    esac
	    break
	fi
    done
} 

function pp_bindings {

    # FIXME: tabwidth

    get_editing_mode

    local command
    local desc
    local emacs_key
    local vi_insert_key
    local vi_command_key
    local require_xdotool
    local head
    local descfile=$CACHE_DIR/keybindings

    if [[ "$BASHRUN_EDITING_MODE" == "vi" ]]; then
	head="$W${BASHRUN_EDITING_MODE}-mode$N\n\n\t${B}insert$N\t${Z}command$W : ${N}description\n"
    else
	head="$W${BASHRUN_EDITING_MODE}-mode$N\n\n\t${B}Keyseq$N\t$W: ${N}Description\n"
    fi
    echo -e $head > $descfile

    for ((i=0; i<${#KEYDESC[@]}; i+=6)); do
	
	command=${KEYDESC[$i]}
	desc=${KEYDESC[$i+1]}
	emacs_key=${KEYDESC[$i+2]}
	vi_insert_key=${KEYDESC[$i+3]}
	vi_command_key=${KEYDESC[$i+4]}
	require_xdotool=${KEYDESC[$i+5]}

	if [[ BASHRUN_XDOTOOL -eq 1 || require_xdotool -eq 0 ]]; then
	    if [[ $BASHRUN_EDITING_MODE == 'emacs' ]]; then
		if [ "$emacs_key" != "" ]; then
		    echo -e "\t$B$emacs_key$W\t:$N $desc" >> $descfile
		fi
	    fi
	    if [[ $BASHRUN_EDITING_MODE == 'vi' ]]; then
		if [[ "$vi_insert_key" != "" || "$vi_command_key" != "" ]]; then
		    echo -e "\t$B$vi_insert_key\t$Z$vi_command_key$W\t$W: $N$desc" >> $descfile
		fi
	    fi
	fi
    done
    echo >> $descfile
}

function bind_keys {

    # prepare tmpfile for bindings
    local bindings="$CACHE_DIR/keys"
    echo -e "# bashrun keybindings\n" > $bindings

    ( cat<<'EOF'
### internal bindings

## emacs keymap

# line editing \C-x0[n]

bind -m emacs '"\C-x00": beginning-of-line'
bind -m emacs '"\C-x01": end-of-line'
bind -m emacs '"\C-x02": kill-line'
bind -m emacs '"\C-x03": yank'
bind -m emacs '"\C-x04":""'

# command execution \C-x1[n]

bind -m emacs '"\C-x10": accept-line'
bind -m emacs '"\C-x11":"\C-x00bashrun-set-mode \"pass\";\C-x10"'
bind -m emacs '"\C-x12":"\C-x00bashrun-set-mode \"su-run";\C-x10"'

# bashrun special functions \C-x2[n]

bind -m emacs '"\C-x20":"\C-x00\C-x02bashrun-set-mode \"bindings\";:\C-x10\C-x03"'
bind -m emacs '"\C-x21":"\C-x00\C-x02bashrun-set-mode \"manual\";:\C-x10\C-x03"'
bind -m emacs '"\C-x22":"\C-x00\C-x02bashrun-set-mode \"handlers\";:\C-x10\C-x03"'
bind -m emacs '"\C-x23":"\C-x00\C-x02bashrun-set-mode \"bookmarks\";:\C-x10\C-x03"'
bind -m emacs '"\C-x24":"\C-x00\C-x02bashrun-set-mode \"about\";:\C-x10\C-x03"'

# bashrun control functions C-x3 and \20[n] (these are invalid characters in iso-latin1)

bind -m emacs -x $'"\201"':"resize up"
bind -m emacs -x $'"\202"':"resize down"
bind -m emacs -x $'"\203"':"resize left"
bind -m emacs -x $'"\204"':"resize right"
bind -m emacs -x $'"\205"':"toggle_size"
bind -m emacs -x $'"\206"':"toggle_debug"
bind -m emacs -x $'"\207"':"bashrun_unmap_window"

## vi-insert keymap

# line editing \C-x0[n]

bind -m vi-insert '"\C-x00": beginning-of-line'
bind -m vi-insert '"\C-x01": end-of-line'
bind -m vi-insert '"\C-x02": kill-line'
bind -m vi-insert '"\C-x03": yank'
bind -m vi-insert '"\C-x04": vi-insertion-mode'

# command execution \C-x1[n]

bind -m vi-insert '"\C-x10": accept-line'
bind -m vi-insert '"\C-x11":"\C-x00bashrun-set-mode \"pass\";\C-x10"'
bind -m vi-insert '"\C-x12":"\C-x00bashrun-set-mode \"su-run";\C-x10"'

# bashrun special functions \C-x2[n]

bind -m vi-insert '"\C-x20":"\C-x00\C-x02bashrun-set-mode \"bindings\";:\C-x10\C-x03"'
bind -m vi-insert '"\C-x21":"\C-x00\C-x02bashrun-set-mode \"manual\";:\C-x10\C-x03"'
bind -m vi-insert '"\C-x22":"\C-x00\C-x02bashrun-set-mode \"handlers\";:\C-x10\C-x03"'
bind -m vi-insert '"\C-x23":"\C-x00\C-x02bashrun-set-mode \"bookmarks\";:\C-x10\C-x03"'
bind -m vi-insert '"\C-x24":"\C-x00\C-x02bashrun-set-mode \"about\";:\C-x10\C-x03"'

# bashrun control functions \20[n] (these are invalid characters in iso-latin1)

bind -m vi-insert -x $'"\201"':"resize up"
bind -m vi-insert -x $'"\202"':"resize down"
bind -m vi-insert -x $'"\203"':"resize left"
bind -m vi-insert -x $'"\204"':"resize right"
bind -m vi-insert -x $'"\205"':"toggle_size"
bind -m vi-insert -x $'"\206"':"toggle_debug"
bind -m vi-insert -x $'"\207"':"bashrun_unmap_window"

## vi-command keymap

# line editing \C-x0[n]

bind -m vi-command '"\C-x00": beginning-of-line'
bind -m vi-command '"\C-x01": end-of-line'
bind -m vi-command '"\C-x02": kill-line'
bind -m vi-command '"\C-x03": yank'
bind -m vi-command '"\C-x04": vi-insertion-mode'

# command execution \C-x1[n]

bind -m vi-command '"\C-x10": accept-line'
bind -m vi-command '"\C-x11":"\C-x00\C-x04bashrun-set-mode \"pass\";\C-x10"'
bind -m vi-command '"\C-x12":"\C-x00\C-x04bashrun-set-mode \"su-run";\C-x10"'

# bashrun special functions \C-x2[n]

bind -m vi-command '"\C-x20":"\C-x00\C-x02\C-x04bashrun-set-mode \"bindings\";:\C-x10\C-x03"'
bind -m vi-command '"\C-x21":"\C-x00\C-x02\C-x04bashrun-set-mode \"manual\";:\C-x10\C-x03"'
bind -m vi-command '"\C-x22":"\C-x00\C-x02\C-x04bashrun-set-mode \"handlers\";:\C-x10\C-x03"'
bind -m vi-command '"\C-x23":"\C-x00\C-x02\C-x04bashrun-set-mode \"bookmarks\";:\C-x10\C-x03"'
bind -m vi-command '"\C-x24":"\C-x00\C-x02\C-x04bashrun-set-mode \"about\";:\C-x10\C-x03"'

# bashrun control functions \20[n] (these are invalid characters in iso-latin1)

bind -m vi-command -x $'"\201"':"resize up"
bind -m vi-command -x $'"\202"':"resize down"
bind -m vi-command -x $'"\203"':"resize left"
bind -m vi-command -x $'"\204"':"resize right"
bind -m vi-command -x $'"\205"':"toggle_size"
bind -m vi-command -x $'"\206"':"toggle_debug"
bind -m vi-command -x $'"\207"':"bashrun_unmap_window"

### user defined bindings

EOF
	) >> $bindings


    # emacs actions
    local emacs_quit='\C-x00\C-x02bashrun-set-mode \"pass\";unset HISTFILE;exit;exit\C-x10'

    if [[ BASHRUN_XDOTOOL -eq 1 ]]; then
	local emacs_abort='a:\207'
    else 	
	local emacs_abort="$emacs_quit"
    fi
    local emacs_pass='\C-x00bashrun-set-mode \"pass\";\C-x10'
    local emacs_run='\C-x00bashrun-set-mode \"run\";\C-x10'
    local emacs_term_run='\C-x00bashrun-set-mode \"run-in-terminal\";\C-x10'
    local emacs_term_hold='\C-x00bashrun-set-mode \"hold-in-terminal\";\C-x10'
    local emacs_term_page='\C-x00bashrun-set-mode \"page-in-terminal\";\C-x10'
    local emacs_su_run='\C-x00bashrun-set-mode \"su-run\";\C-x10'
    local emacs_su_term_run='\C-x00bashrun-set-mode \"su-run-in-terminal\";\C-x10'
    local emacs_su_term_hold='\C-x00bashrun-set-mode \"su-hold-in-terminal\";\C-x10'
    local emacs_su_term_page='\C-x00bashrun-set-mode \"su-page-in-terminal\";\C-x10'
    local emacs_show_manual='\C-x00bashrun-set-mode \"show-manual\";\C-x10'
    local emacs_show_info='\C-x00bashrun-set-mode \"show-info"\;\C-x10'
    local emacs_show_help='\C-x00bashrun-set-mode \"show-help-builtin\";\C-x10'
    local emacs_copy='\C-x00bashrun-set-mode \"copy\" 0;\C-x10'
    local emacs_copy_paste='\C-x00bashrun-set-mode \"copy-and-paste\" 0;\C-x10'
    local emacs_filter='\C-x00bashrun-set-mode \"filter\" 0;\C-x10'
    local emacs_filter_paste='\C-x00bashrun-set-mode \"filter-and-paste\" 0;\C-x10'
    local emacs_dict_lookup='\C-x00bashrun-set-mode \"dict-lookup\" 0;\C-x10'
    local emacs_browse='\C-x00bashrun-set-mode \"browse\" 0;\C-x10'
    local emacs_google_search='\C-x00bashrun-set-mode \"google-search\" 0;\C-x10'
    local emacs_bashrun_bindings='\C-x20'
    local emacs_bashrun_manual='\C-x21'
    local emacs_bashrun_handlers='\C-x22'
    local emacs_resize_up='a:\201'
    local emacs_resize_down='a:\202'
    local emacs_resize_left='a:\203'
    local emacs_resize_right='a:\204'
    local emacs_toggle_size='a:\205'
    local emacs_bashrun_debug='a:\206'

    # vi-insert actions
    local vi_insert_quit='\C-x00\C-x02bashrun-set-mode \"pass\";unset HISTFILE;exit;exit\C-x10'

    if [[ BASHRUN_XDOTOOL -eq 1 ]]; then
	local vi_insert_abort='a:\207'
    else 	
	local vi_insert_abort="$vi_insert_quit"
    fi
    local vi_insert_pass='\C-x00bashrun-set-mode \"pass\";\C-x10'
    local vi_insert_run='\C-x00bashrun-set-mode \"run\";\C-x10'
    local vi_insert_term_run='\C-x00bashrun-set-mode \"run-in-terminal\";\C-x10'
    local vi_insert_term_hold='\C-x00bashrun-set-mode \"hold-in-terminal\";\C-x10'
    local vi_insert_term_page='\C-x00bashrun-set-mode \"page-in-terminal\";\C-x10'
    local vi_insert_show_manual='\C-x00bashrun-set-mode \"show-manual\";\C-x10'
    local vi_insert_show_info='\C-x00bashrun-set-mode \"show-info\";\C-x10'
    local vi_insert_show_help='\C-x00bashrun-set-mode \"show-help-builtin\";\C-x10'
    local vi_insert_copy='\C-x00bashrun-set-mode \"copy\" 0;\C-x10'
    local vi_insert_copy_paste='\C-x00bashrun-set-mode \"copy-and-paste\" 0;\C-x10'
    local vi_insert_filter='\C-x00bashrun-set-mode \"filter\" 0;\C-x10'
    local vi_insert_filter_paste='\C-x00bashrun-set-mode \"filter-and-paste\" 0;\C-x10'
    local vi_insert_dict_lookup='\C-x00bashrun-set-mode \"dict-lookup\" 0;\C-x10'
    local vi_insert_browse='\C-x00bashrun-set-mode \"browse\" 0;\C-x10'
    local vi_insert_google_search='\C-x00bashrun-set-mode \"google-search\" 0;\C-x10'
    local vi_insert_bashrun_bindings='\C-x20'
    local vi_insert_bashrun_manual='\C-x21'
    local vi_insert_bashrun_handlers='\C-x22'
    local vi_insert_resize_up='a:\201'
    local vi_insert_resize_down='a:\202'
    local vi_insert_resize_left='a:\203'
    local vi_insert_resize_right='a:\204'
    local vi_insert_toggle_size='a:\205'
    local vi_insert_bashrun_debug='a:\206'

    # vi-command actions
    local vi_command_quit='\C-x00\C-x04\C-x02bashrun-set-mode \"pass\";unset HISTFILE;exit;exit\C-x10'

    if [[ BASHRUN_XDOTOOL -eq 1 ]]; then
	local vi_command_abort='a:\207'
    else 	
	local vi_command_abort="$vi_command_quit"
    fi
    local vi_command_pass='\C-x00\C-x04bashrun-set-mode \"pass\";\C-x10'
    local vi_command_run='\C-x00\C-x04bashrun-set-mode \"run\";\C-x10'
    local vi_command_term_run='\C-x00\C-x04bashrun-set-mode \"run-in-terminal\";\C-x10'
    local vi_command_term_hold='\C-x00\C-x04bashrun-set-mode \"hold-in-terminal\";\C-x10'
    local vi_command_term_page='\C-x00\C-x04bashrun-set-mode \"page-in-terminal\";\C-x10'
    local vi_command_su_run='\C-x00\C-x04bashrun-set-mode \"su-run\";\C-x10'
    local vi_command_su_term_run='\C-x00\C-x04bashrun-set-mode \"su-run-in-terminal\";\C-x10'
    local vi_command_su_term_hold='\C-x00\C-x04bashrun-set-mode \"su-hold-in-terminal\";\C-x10'
    local vi_command_su_term_page='\C-x00\C-x04bashrun-set-mode \"su-page-in-terminal\";\C-x10'
    local vi_command_show_manual='\C-x00\C-x04bashrun-set-mode \"show-manual\";\C-x10'
    local vi_command_show_info='\C-x00\C-x04bashrun-set-mode \"show-info\";\C-x10'
    local vi_command_show_help='\C-x00\C-x04bashrun-set-mode \"show-help-builtin\";\C-x10'
    local vi_command_copy='\C-x00\C-x04bashrun-set-mode \"copy\" 0;\C-x10'
    local vi_command_copy_paste='\C-x00\C-x04bashrun-set-mode \"copy-and-paste\" 0;\C-x10'
    local vi_command_filter='\C-x00\C-x04bashrun-set-mode \"filter\" 0;\C-x10'
    local vi_command_filter_paste='\C-x00\C-x04bashrun-set-mode \"filter-and-paste\" 0;\C-x10'
    local vi_command_dict_lookup='\C-x00\C-x04bashrun-set-mode \"dict-lookup\" 0;\C-x10'
    local vi_command_browse='\C-x00\C-x04bashrun-set-mode \"browse\" 0;\C-x10'
    local vi_command_google_search='\C-x00\C-x04bashrun-set-mode \"google-search\" 0;\C-x10'
    local vi_command_bashrun_bindings='\C-x20'
    local vi_command_bashrun_manual='\C-x21'
    local vi_command_bashrun_handlers='\C-x22'
    local vi_command_resize_up='a:\201'
    local vi_command_resize_down='a:\202'
    local vi_command_resize_left='a:\203'
    local vi_command_resize_right='a:\204'
    local vi_command_toggle_size='a:\205'
    local vi_command_bashrun_debug='a:\206'

    local line
    local linenum=0
    local lines=()
    local command
    local keymap
    local keyseq
    local keyname
    local prefix
    local action
    local type
    local binding

    # read keyconf
    # comment empty lines to get correct linenums
    # add a last line in case of a missing newline at EOF
    OLDIFS=$IFS
    IFS=$'\n' 
    lines=($(cat $KEYCONF | sed 's/^$/#/' && echo -e '\n#' ))
    IFS=$OLDIFS
    
    # reserved bindings...
    local reserved=('\201' '\202' '\203' '\204' '\205' '\206' '\207' '\208' '\209')
    local a=0; local b=0
    for (( a=0; a<10; a++)); do
	for (( b=0; b<10; b++)); do
	    reserved[${#reserved[@]}]="\C-x${a}${b}"
	done
    done
	
    for (( i=0; i<${#lines[@]}; i+=1 )); do

	line=${lines[$i]}
	let linenum+=1

        # skip comments & empty lines
	[[ "${line:0:1}" == "#" ]] && continue
	[[ "$line" =~ ^\ +$ ]] && continue
	[[ -z "$line" ]] && continue

	# parse KEYMAP command
	if [[ "$line" =~ ^KEYMAP\ +(.+) ]]; then
	    keymap=${BASH_REMATCH[1]}
	    continue
	fi

	# parse keybinding
	if [[ "$line" =~ ^([^\ ]+)\ +([^ ]+)\ +?([^ ]+)? ]]; then

	    # get command name and keyseq
	    command=${BASH_REMATCH[1]}
	    keyseq=${BASH_REMATCH[2]}
	    keyname=${BASH_REMATCH[3]}
	    
	    # get keyname
	    if [ "$keyname" == "" ]; then
		keyname=`keyseq2keyname "$keyseq"`
	    fi

	    # check for reseverd binding
	    for r in ${reserved[@]}; do
		if [[ "$r" == "$keyseq" ]]; then
		    bashrun_show_error "Error in keyboard configuration:\n\nThe key sequence $keyname is reserved for internal use. Please choose a different key sequence for the '$command' action in $keymap-mode.\n\nlocation: $KEYCONF, line $linenum"
		    continue
		fi
	    done

	    update_keydesc "$command" "$keyname" "$keymap"

	    # get action
	    prefix=${keymap/-/_}
	    command=${command//-/_}
	    action="${prefix}_$command"
	    action="${!action}"

	    # determine type (either macro or command)
	    # actions prefixed with "-x:" become commands
	    type="macro"

	    if [[ "${action:0:3}" == "-x:" ]]; then
		action="${action/-x:/}"
		type="command"
	    fi

	    # actions prefixed whith "a:" become aliases
	    if [[ "${action:0:2}" == "a:" ]]; then
		action="${action/a:/}"
		type="alias"
	    fi

	    if [[ ! -z "$action" ]]; then

	        # create binding
		if [ "$type" == "macro" ]; then
		    binding="bind -m $keymap '\"$keyseq\":\"$action\"'"
		    
		elif [ "$type" == "command" ]; then
		    binding="bind -m $keymap -x '\"$keyseq\": $action'"
		    
		elif [ "$type" == "alias" ]; then
		    binding="bind -m $keymap '\"$keyseq\"':\$'\"$action\"'"
		fi
		echo "$binding" >> $bindings
	    fi
	else
	    bashrun_show_error "Error in keyboard configuration:\n\nSyntax error: line: '${line}' -> skipped.\n\nlocation: $KEYCONF, line $linenum"
	fi
    done
    
    # disable XON/XOFF flow control (allows readline to bind C-s and C-q)
    stty -ixon	
    
    # source bindings
    source $bindings
}
bind_keys

pp_handlers 
pp_bookmarks
