## Copyright (C) 2005-2014 Deepayan Sarkar <Deepayan.Sarkar@R-project.org>
###
### This file provides programmable completion for R under the bash
### shell.  It is made available under the terms of the GNU General
### Public License, version 2, or at your option, any later version,
### incorporated herein by reference.
###
### 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, available at
### http://www.gnu.org/licenses/licenses.html, for more details.


### NOTE: This does NOT provide completion for commands within R, only
### for R's command-line options.  For example,
###
### R CMD build --f[TAB]
###
### will complete to --force and so on. In other words, this is
### potentially useful (for the most part) to package developers, not
### regular users.

### NEWS: Updated for R 2.4.0 (October 2006)

### BUGS: As it stands, this is far from perfect.  Known bugs include:
###
### (1) Not all mutually exclusive options are properly handled
###
### (2) Doesn't test for the existence of R before defining the
### (2) completion function (not strictly a bug, but definitely not a
### (2) good feature)


### INSTALLATION AND USAGE: You need to first install and enable bash
### completion on your system to use this script.  See
###
### http://www.caliban.org/bash/index.shtml#completion
###
### to learn more about bash completion.  On a modern Debian system,
### all the necessary files are already installed by the bash package.
### To enable it, just uncomment the following lines in your .bashrc
###
### # if [ "$PS1" -a -f /etc/bash_completion ]; then
### #     . /etc/bash_completion
### # fi
###
### or to try it out in your current shell, type
###
### . /etc/bash_completion
###
### Once you are using bash completion, you can enable completion for
### R by similarly sourcing this file.  For permanent use, place it in
### your $BASH_COMPLETION_DIR (/etc/bash_completion.d in Debian).  If
### you don't have access to do that, copy the contents of this file
### in it's entirety to your ~/.bash_completion, which is sourced by
### the bash_completion script.




_R()
{
    local cur loneOpts helpOpts stdOpts argOpts versOpts cmds cmd furtherOpts
    local fileNames dirNames i lastToken
    # local IFS=$'\t\n'

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}

    loneOpts='--version RHOME'
    versOpts='-v --version' # only on commands
    helpOpts='-h --help'

    stdOpts='--save --no-save --no-environ --no-site-file            \
    --no-init-file --restore --no-restore-data --no-restore-history  \
    --no-restore --vanilla --no-readline -q --quiet --silent --slave \
    --interactive --verbose -d -g --args -f -e'

    ## these should end with a '=' and not be followed by a space.
    ## Unfortunately, I haven't figured out how to make that work.
    ## -nospace doesn't suppress the space, and if I add = at the end,
    ## they get completed as "--debugger\= " etc.  Anyway, they are
    ## kept separate in anticipation of some future fix...

    argOpts='--min-vsize --max-vsize --min-nsize --max-nsize \
    --encoding --max-ppsize --debugger --debugger-args \
    --gui --arch --file'

    cmds='BATCH COMPILE SHLIB INSTALL REMOVE build check LINK Rprof \
    Rdconv Rd2pdf Rd2txt Stangle Sweave Rdiff config javareconf rtags'




    fileNames='' # possible to have files in certain commands (e.g. BATCH)
    dirNames=''


    if [[ $COMP_CWORD -eq 1 ]] ; then
	COMPREPLY=(
	    $( compgen -W "CMD $loneOpts $helpOpts $stdOpts" -- $cur )
	    $( compgen -o nospace -W "$argOpts" -- $cur )
	)
	return 0
    fi

    ## certain tokens preclude further activity
    if [[ ${COMP_WORDS[$COMP_CWORD-1]} == @(-v|--version|RHOME|-h|--help) ]] ; then
	return 0
    fi

    ## more complicated stuff
    ## keep last token for use later
    lastToken=${COMP_WORDS[$COMP_CWORD-1]}


    ## if [[ $COMP_CWORD -gt 1 ]] && (implied)
    if [[ ${COMP_WORDS[1]} == "CMD" ]] ; then

	if [[ $COMP_CWORD -eq 2 ]] ; then
	    COMPREPLY=(
		$( compgen -W "$cmds" -- $cur )
	    )
	    return 0
	else

	    ## COMP_CWORD >= 3.  cmd=${COMP_WORDS[2]} is (hopefully)
	    ## one of $cmds.  At this point, all further options
	    ## (except -v|-h|RHOME etc handled above) will be assumed
	    ## repeatable (except for duplicates and exclusives, to be
	    ## handled later).  In the next step, possible completions
	    ## for each cmd will be derived.

	    ## A largely similar derivation is also needed for the
	    ## case when ${COMP_WORDS[2]} is not CMD.  A similar
	    ## completion list will be derived in that case.
	    ## Afterwards, things will be removed from these as
	    ## needed, before the final COMPREPLY is computed.

	    cmd=${COMP_WORDS[2]}
	    case $cmd in
		BATCH)

		    ## if last word is a filename (not starting with
		    ## -) then no more options are allowed, but can be
		    ## followed by any file (outfile)

		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken != -* ]] ; then

			furtherOpts=""
			fileNames=$( compgen -f -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    else

			furtherOpts="$versOpts $helpOpts $stdOpts $argOpts --no-timing"
			fileNames=$( compgen -f -X '!*.@(R|r|S|s)' -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    fi
		    ;;

		COMPILE)
		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken != -* ]] ; then

			furtherOpts=""
			fileNames=$( compgen -f -X '!*.@(c|cc|cpp|C|f)' -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    else

			furtherOpts="$versOpts $helpOpts -D"
			fileNames=$( compgen -f -X '!*.@(c|cc|cpp|C|f)' -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    fi
		    ;;

		SHLIB)
		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken != -* ]] ; then

			furtherOpts=""
			fileNames=$( compgen -f -X '!*.@(a|o)' -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    else

			furtherOpts="$versOpts $helpOpts -o --output -c --clean \
 --preclean -n --dry-run"
			fileNames=$( compgen -f -X '!*.@(a|o)' -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    fi

		    ;;

		INSTALL)
		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken == @(-l|--library) ]] ; then

			furtherOpts=""
			dirNames=$( compgen -d -- "$cur" )

		    else

			furtherOpts="$versOpts $helpOpts                \
--configure-args --configure-vars -c --clean --preclean -d --debug -l  	\
--library --no-configure --no-docs --html --no-html --latex --example	\
--fake --no-lock --lock --pkglock --build --install-tests --no-R	\
--no-libs --no-data --no-help --no-demo --no-exec --no-inst		\
--no-multiarch --libs-only --data-compress --resave-data		\
--compact-docs --with-keep.source --without-keep.source --byte-compile	\
--no-byte-compile --no-test-load --no-clean-on-error --merge-multiarch  \
--built-timestamp"
			fileNames=$( compgen -f -X '!*.@(tar.gz|tgz)' -- "$cur" )
			dirNames=$( compgen -d -- "$cur" )

		    fi
		    ;;

		REMOVE)
		    furtherOpts="$versOpts $helpOpts -l --library"
		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken == @(-l|--library) ]] ; then
			dirNames=$( compgen -d -- "$cur" )
		    fi
		    ;;

		build)

		    furtherOpts="$versOpts $helpOpts \
--force --keep-empty-dirs --no-build-vignettes --no-manual --resave-data \
--no-resave-data --compact-vignettes --md5"
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

		check)

		    furtherOpts="$versOpts $helpOpts           \
-l --library -o --output				       \
--no-clean --no-codoc --no-examples --no-install --no-tests    \
--no-manual --no-vignettes --no-build-vignettes --run-dontrun  \
--run-donttest --use-gct --use-valgrind --timings              \
--install-args --test-dir --check-subdirs --as-cran            \
--extra-arch --multiarch --no-multiarch --force-multiarch"

		    fileNames=$( compgen -f -X '!*.@(tar.gz|tgz)' -- "$cur" )
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

		LINK)
		    furtherOpts="$versOpts $helpOpts"
		    ;;

		Rprof)
		    furtherOpts="$versOpts $helpOpts --lines --total --self --linesonly --min%total --min%self"
		    fileNames=$( compgen -f -- "$cur" )
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

		Rdconv)

		    furtherOpts="$versOpts $helpOpts -t --type \
		    --encoding --package -o --output --os --OS"

		    fileNames=$( compgen -f -X '!*.@(Rd)' -- "$cur" )
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

		Rd2pdf)

		    furtherOpts="$versOpts $helpOpts --batch            \
		    --no-clean --no-preview --encoding --outputEncoding \
                    --os --OS -o --output --force --title               \
		    --no-index --no-description --internals"

		    fileNames=$( compgen -f -X '!*.@(Rd)' -- "$cur" )
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

		Rd2txt)
		    furtherOpts="$versOpts $helpOpts -t --type --encoding --package -o --output --os --OS"
		    fileNames=$( compgen -f -X '!*.@(Rd)' -- "$cur" )
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

		Sweave)

		    ## if last word is a filename (not starting with
		    ## -) then nothing more is allowed

		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken != -* ]] ; then

			return 0

		    else

			furtherOpts="$versOpts $helpOpts --driver --engine --encoding --options --pdf --compact"
			fileNames=$( compgen -f -X '!*.@(Rnw|rnw|Snw|snw|Rmd|rmd)' -- "$cur" )
			dirNames=""
			## dirNames=$( compgen -d -- "$cur" )

		    fi
		    ;;

		Stangle)

		    ## if last word is a filename (not starting with
		    ## -) then nothing more is allowed

		    if [[ $COMP_CWORD -gt 3 ]] && \
			[[ $lastToken != -* ]] ; then

			return 0

		    else

			furtherOpts="$versOpts $helpOpts --engine --encoding --options"
			fileNames=$( compgen -f -X '!*.@(Rnw|rnw|Snw|snw|Rmd|rmd)' -- "$cur" )
			dirNames=""
			## dirNames=$( compgen -d -- "$cur" )

		    fi
		    ;;

		config)

		    if [[ $COMP_CWORD -gt 3 ]] ; then

			return 0

		    else

			furtherOpts="$versOpts $helpOpts --ldflags  --cppflags  \
--no-user-files --no-site-files                                                 \
BLAS_LIBS CC CFLAGS CPICFLAGS CPP CPPFLAGS CXX CXXCPP CXXFLAGS			\
CXXPICFLAGS DYLIB_EXT DYLIB_LD DYLIB_LDFLAGS F77 FFLAGS FLIBS			\
FPICFLAGS FC FCFLAGS FCPICFLAGS JAR JAVA JAVAC JAVAH JAVA_HOME			\
JAVA_LIBS JAVA_CPPFLAGS LAPACK_LIBS LIBnn LDFLAGS OBJC OBJCFLAGS MAKE		\
SAFE_FFLAGS SHLIB_CFLAGS SHLIB_CXXLD SHLIB_CXXLDFLAGS SHLIB_EXT			\
SHLIB_FFLAGS SHLIB_LD SHLIB_LDFLAGS SHLIB_FCLD SHLIB_FCLDFLAGS			\
TCLTK_CPPFLAGS TCLTK_LIBS"

		    fi
		    ;;


		javareconf)
		    furtherOpts="$versOpts $helpOpts -n --dry-run -e"
		    ;;


		rtags)
		    furtherOpts="$versOpts $helpOpts -o --output --no-c --no-R \
                                --no-Rd -a --append -V --verbose"
		    dirNames=$( compgen -d -- "$cur" )
		    ;;

	    esac

	fi

    elif [[ $lastToken == "<" ]] ; then

	## R [options] < <need .R file here>
	furtherOpts=""
	fileNames=$( compgen -f -X '!*.@(R|r|S|s)' -- "$cur" )
	dirNames=$( compgen -d -- "$cur" )

    elif [[ $lastToken == ">" ]] ; then

	## R [options] > <need output file here>
	furtherOpts=""
	fileNames=$( compgen -f -- "$cur" )
	dirNames=$( compgen -d -- "$cur" )

    else

	case $lastToken in

	    -g|--gui)
		furtherOpts="X11 Tk"
		;;
	    -d|--debugger) ## allow commands
		furtherOpts=$( compgen -c -- "$cur" )
		;;

	    -f|--file)
		fileNames=$( compgen -f -X '!*.@(R|r|S|s)' -- "$cur" )
		dirNames=""
		## dirNames=$( compgen -d -- "$cur" )
		;;

	    *) ## NOT 'R CMD <something> ': see comments above
		furtherOpts="$versOpts $helpOpts $stdOpts $argOpts" ;;

	esac
    fi

    ## need some removal

    ## take out options already given
    for (( i=1; i<=$COMP_CWORD-1; ++i )) ; do

	opt=${COMP_WORDS[$i]}

	case $opt in
	    --*)    optBase=${opt/=*/} ;;
	    -*)     optBase=${opt:0:2} ;;
	esac

	## can't seem to do this using case
	if [[ $opt == "<" ]] ; then
	    ## no more args allowed
	    furtherOpts=""
	elif [[ $opt == ">" ]] ; then
	    ## no more args allowed
	    furtherOpts=""
	fi


	allOpts=" $furtherOpts "
	allOpts=${allOpts/ ${optBase} / }

	#echo $allOpts

        ## take out alternatives and mutually exclusives
	case $optBase in

# not working
# 	    "<") ## only filenames allowed
# 		echo "-----------"
# 		allOpts=">" ;;
# 	    ">") ## only filenames allowed
# 		allOpts="<" ;;

	    -q|--quiet|--silent)
		allOpts=${allOpts/ -q / }
		allOpts=${allOpts/ --quiet / }
		allOpts=${allOpts/ --silent / }
		#echo $allOpts

		;;
	    --save|--no-save)
		allOpts=${allOpts/ --save / }
		allOpts=${allOpts/ --no-save / }
		;;
	    --vanilla)
		allOpts=${allOpts/ --no-save / }
		allOpts=${allOpts/ --no-restore / }
		allOpts=${allOpts/ --no-site-file / }
		allOpts=${allOpts/ --no-init-file / }
		allOpts=${allOpts/ --no-environ / }
		;;
	    -d)               allOpts=${allOpts/ --debugger / } ;;
	    --debugger)       allOpts=${allOpts/ -d / } ;;
	    --restore)        allOpts=${allOpts/ --no-restore / } ;;
	    --no-restore)     allOpts=${allOpts/ --restore / } ;;
	    -g|--gui)
		allOpts=${allOpts/ -g / }
		allOpts=${allOpts/ --gui / }
		;;

 	    BATCH)
		allOpts=${allOpts/ --restore / }
		allOpts=${allOpts/ --save / }
		allOpts=${allOpts/ --no-readline / }
		;;

	esac

    done

    ## and finally:

    COMPREPLY=(
	$( compgen -W "$allOpts $fileNames $dirNames" -- $cur )
    )
    return 0


}

complete -F _R -o default R

## Add more aliases here, e.g.

# for e in R-devel R-patched; do complete -F _R -o default ${e}; done



### Local variables:
### mode: shell-script
### End:
