# Common funcitons of Bash auto-completion in Gnuastro. For more details on
# completion, see the "autocomplete feature" section under the "Developing"
# chapter of Gnuastro's manual and the comments below.
#
# This script contains generic functions that can be used by all the
# programs. Each program also has its own '*-complete.bash' file that will
# use the generic functions here and define all the internal variables that
# these functions take as input/output. During the installation, all those
# '*-complete.bash' files will be appended to this (and installed as one
# file to be loaded into the user's '.bashrc').
#
# Because the combined file is loaded into the user's Bash environment
# every time, we don't want to complicate the user's environment with
# global environment variables. As a result, all the functions used in the
# Bash auto-completion should be 'local' (to that particular function and
# the functions it calls).
#
# To debug/test this script, you can take these steps after building
# Gnuastro in the program's 'bin/progname/astprogname-complete.bash'
# file. Building Gnuastro is necessary because one of the files
#
#    1. Uncomment the two 'source' lines in the program's completion
#       script.
#    2. Correct the un-commented locations. Note that the second is an
#       automatically built file (it is not under version control or in the
#       source directory). So in case you have defined a separate build
#       directory, give that directory. If you are building within your
#       source (which is generally a bad idea!), it will be the same
#       directory.
#    3. Give a value to the 'gnuastro_prefix' variable within
#       '_gnuastro_autocomplete_astprogname'. This is the location that
#       Gnuastro is installed in your OS (usually '/usr/local/bin').
#    4. Activate the program's script with this command in the terminal you
#       are testing: 'source bin/progname/astprogname-complete.bash' (here
#       assuming you are in the top Gnuastro source directory, you can run
#       it from anywhere, just correct the locatation).
#
# Original author:
#     Pedram Ashofteh-Ardakani <pedramardakani@pm.me>
# Contributing author(s):
#     Mohammad Akhlaghi <mohammad@akhlaghi.org>
# Copyright (C) 2021-2025 Free Software Foundation, Inc.
#
# Gnuastro 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 3 of the License, or (at your option)
# any later version.
#
# Gnuastro 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 Gnuastro. If not, see <http://www.gnu.org/licenses/>.





#######################################################################
############      Options and general operating mode       ############
#######################################################################

# Basic initialization.
_gnuastro_autocomplete_initialize(){

    # Initialize the completion response with null
    COMPREPLY=();

    # Variable "current", is the current word being completed. "$2" is the
    # default value for the current word in completion scripts. But we are
    # using the longer form: "${COMP_WORDS[COMP_CWORD]}" for clarity.
    current="${COMP_WORDS[COMP_CWORD]}"
    if [ "$current" = "=" ]; then

        # The equal sign '=' raises complexities when filling suggestions
        # for long options. Things will work out fine when they are simply
        # ignored.
        current=""

    fi

    # Variable "prev", is one word before the one being completed. By
    # default, this is set as "$3" in completion scripts. But we are using
    # the longer form: "${COMP_WORDS[COMP_CWORD-1]}" to avoid confusions
    # with the arguments of our internal functions. Note that Bash will
    # return the '=' sign as a separate component of the line, so in that
    # case, we want the second-last word.
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    if [ "$prev" = "=" ]; then

        # While a user is writing a long option's argument, the previous
        # word will be the equal sign '='. This is not helpful at all. But
        # looking at a word just before '=' helps us understand which long
        # option is being called upon.
        prev="${COMP_WORDS[COMP_CWORD-2]}"

    fi
}





# List all the options of the given program (an '=' is kept for the options
# that need a value). In the output of '--help', option lines have these
# properties:
#
#   - The full line starts with two empty spaces.
#   - The first non-white character is a '-'.
#   - It contains a long option formated like '--XXXX'.
#
# But some options have a short version (which we ignore in
# autocompletion), so if the first word ends with a ',' the option name we
# want to show is the second word.
_gnuastro_autocomplete_option_list(){
    options_all=$("${COMP_WORDS[0]}" --help \
                      | awk '/^  / && $1 ~ /^-/ && /--+[a-zA-Z0-9]*/ { \
                              if($1 ~ /,$/) name=$2; \
                              else          name=$1; \
                              print name}' \
                      | sed -e's|=.*|=|');
}





# Return successfully if the previous token (specified as first argument)
# is an option that requires a value and store the option name if it is.
#
#   INPUT:
#     1) [as argument] String to search for.
#     *) [as variable] 'options_all' (in case the list of options is
#                      already read)
_gnuastro_autocomplete_string_is_valued_option(){

    # For easy reading.
    local string=$1

    # If the first character of the string isn't a '-', then its not an
    # option and there is no need to do any futher checks (and slow down
    # the output) so we can safely return failure.
    if [ ${string:0:1} != "-" ]; then return 1; fi

    # List of option names (with an '=' after those that need a value.
    if [ x"$options_all" = x ]; then
        _gnuastro_autocomplete_option_list
    fi

    # Go over the option list and see if they match the '$1'.
    for option in $options_all; do
        if [[ $option = $string=* ]]; then return 0; fi
    done

    # If control reaches here, then return failure (1).
    return 1
}





# See if the current word is an argument or option value.
_gnuastro_autocomplete_mode_arg_optval(){

    # If the previous token is the first token, then this is an
    # argument, no need for any further checks.
    if [ $prev = "${COMP_WORDS[0]}" ]; then
        argument=$current

    # If the previous token is an option that needs a value, then this is
    # an option value, this function will set 'option_name' and
    # 'option_name_complete' if necessary.
    elif _gnuastro_autocomplete_string_is_valued_option $prev; then
        option_name=$prev
        option_value=$current
        option_name_complete=1

    # The previous token wasn't an option that required a value, so this is
    # an argument.
    else
        argument=$current
    fi
}





# See if this is an argument, option name or option value. This function
# will fill 'argument', 'option_name' and 'option_value' (they should be
# initialized to an empty string before it).
#
#  option_value=FULL:            We are busy completing an option value.
#  option_name=FULL:             Can mean different meanings.
#    {
#      option_name_complete==0:  We are still filling the option name.
#      option_name_complete==1:  We are starting the option values.
#    }
#  argument=FULL:                We are busy filling an argument.
#  argument=EMPTY:               We haven't started writing an argument yet.
_gnuastro_autocomplete_mode(){

    # Local variable necessary only in this function.
    local namevalue=""

    # If the current word is empty, it may be an argument, or value of an
    # option (in case a value-required option is given before).
    if [ x$current = x ]; then
        _gnuastro_autocomplete_mode_arg_optval

    # The current word isn't empty.
    else

        # If the current word starts with a '-', it is an option name or
        # 'name=value' pair.
        if [ ${current:0:1} = "-" ]; then

            # If there is an equal sign, then we should separate the option
            # name from the value and keep
            if [[ $current = *=* ]]; then

                # By setting the "internal field separator" (IFS) to '='
                # and using 'read', we can separate the strings before and
                # after the equal sign.
                IFS="=" read -ra namevalue <<< $current
                option_name=${namevalue[0]}
                option_name_complete=1

                # If the value isn't written yet, (for example '--hdu='),
                # then the second string will just be an '='. But no value
                # is given yet, so 'option_value' should be empty.
                option_value=${namevalue[1]}
                if [ x$option_value = x"\=" ]; then option_value=""; fi
            else
                option_name=$current
                option_name_complete=0
            fi

        # The current word didn't start with a '-', so it may be an
        # argument or option value.
        else
            # Bash may separate the '=' in 'name=value' tokens. In this
            # scenario, when the user only gives 'name=' and presses TAB,
            # then 'current' will be '='. In this case, we should just set
            # it to empty.
            if [ $current = "=" ]; then current=""; fi

            # Check to see if its an argument or option value.
            _gnuastro_autocomplete_mode_arg_optval
        fi

    fi
}




# Given a certain option (as first argument), find the value that the user
# has given for it.
#
# OUTPUT:
#   read_option_value
_gnuastro_autocomplete_read_option_value(){

    # Inputs:
    local read_option_name=$1

    # Initialize the output (defined as 'local' before this).
    read_option_value=""

    # Parse through the given command-line and find the value.
    local option_found=0
    for word in ${COMP_WORDS[*]}; do

        # Ignore the program name (first word), current (last) word and any
        # '=' signs.
        if [ x$word = x${COMP_WORDS[0]} ] \
               || [ x$word = x$current ] \
               || [ x$word = x"=" ]; then
            local just_a_place_holder=1
        else
            # If the 'option_found' flag is set, this is the answer, set it
            # and return (this has to be *before* the place that we set
            # 'option_found').
            if [ $option_found = 1 ]; then
                read_option_value="$word";
                return 0
            fi

            # If this word is the desired option, set the 'option_found'
            # flag so the next word is taken as the value.
            if [ x$word = x$read_option_name ]; then option_found=1; fi
        fi
    done
}




















#######################################################################
############                     Files                     ############
#######################################################################

# Check if the given file is a FITS file (that can actually be
# opened). Note that FITS files have many possible extensions (see the
# 'gal_fits_name_is_fits' function in 'lib/fits.c').
_gnuastro_autocomplete_is_fits(){
    if "$gnuastro_prefix"/astfits "$1" &> /dev/null; then return 0;
    else                                                  return 1;
    fi
}





# Return successfully if argument (a FITS file) has a image HDU.
_gnuastro_autocomplete_fits_has_image(){
    if _gnuastro_autocomplete_is_fits "$1"; then
        if [ $("$gnuastro_prefix"/astfits "$1" --hasimagehdu) = 1 ]; then
            return 0
        fi
    fi
    return 1
}





# Return successfully if argument (a FITS file) has a table HDU.
_gnuastro_autocomplete_fits_has_table(){
    if _gnuastro_autocomplete_is_fits "$1"; then
        if [ $("$gnuastro_prefix"/astfits "$1" --hastablehdu) = 1 ]; then
            return 0
        fi
    fi
    return 1
}





# Return successfully if first argument is plain-text (not binary).
_gnuastro_autocomplete_is_plaintext(){
    if file "$1" | grep 'executable\|binary' &> /dev/null; then return 1;
    else                                                        return 0;
    fi
}





# Return successfully (with 0) if the given non-FITS file is a table.
_gnuastro_autocomplete_is_plaintext_table(){

    # Only do the check if the file exists.
    if [ -f "$1" ]; then

        # If the file is not plain-text, it will contain an 'executable' or
        # 'binary' in the output of the 'file' command.
        if _gnuastro_autocomplete_is_plaintext "$1"; then

            # The file is plain-text. Extract the first non-commented or
            # empty line and feed it to 'asttable' to see if it can be
            # interpretted properly. We don't want to bother with the other
            # lines, because we don't want to waste computational power
            # here.
            if awk '!/^#/ && NF>0 {print; exit 0}' "$1" \
                    | "$gnuastro_prefix"/asttable &> /dev/null; then
                return 0
            else
                return 1
            fi

            # The file was binary
        else return 1
        fi

    # The file didn't exist.
    else return 1
    fi
}





# Return successfully if the first argument is a table.
_gnuastro_autocomplete_is_table(){
    if   _gnuastro_autocomplete_fits_has_table     "$1"; then return 0
    elif _gnuastro_autocomplete_is_plaintext_table "$1"; then return 0
    else                                                      return 1
    fi
}





# If a certain file (image, table, or certain other files) is already given
# in the previous tokens, return successfully (with zero), and will put its
# name in 'given_file'. Otherwise, return a failure (1) and 'given_file'
# will be untouched.
_gnuastro_autocomplete_first_in_arguments(){

    # Inputs
    local mode=$1

    # Local variables (that are only for this function).
    local word=""
    local previous=""

    # Initialize outputs
    given_file=""

    # Go over all the words/tokens given until now.
    for word in ${COMP_WORDS[*]}; do

        # Ignore the program name (first word), current (last) word, any
        # directories or '=', or any word that starts with a '-' (which is
        # an option).
        if [ x$word = x${COMP_WORDS[0]} ] \
               || [ x$word = x$current ] \
               || [ ${word:0:1} = "-" ] \
               || [ x$word = x"=" ] \
               || [ -d $word ]; then
            local just_a_place_holder=1
        else
            # If the previous word is a valued option, then it shouldn't be
            # checked.
            if _gnuastro_autocomplete_string_is_valued_option $previous; then
                local just_a_place_holder=1

            # Previous word was not a valued option, do the operation based
            # on the mode.
            else
                case "$mode" in
                    fits)
                        if _gnuastro_autocomplete_is_fits "$word"; then
                            given_file="$word"
                        fi
                        ;;
                    image)
                        if _gnuastro_autocomplete_fits_has_image "$word"; then
                            given_file="$word"
                            return 0;
                        fi
                        ;;
                    table)
                        if _gnuastro_autocomplete_is_table "$word"; then
                            given_file="$word"
                            return 0;
                        fi
                        ;;
                    source_c)
                        if $(echo "$word" | grep "\.c$" &> /dev/null) \
                                && [ -f "$word" ]; then
                            given_file="$word"
                            return 0;
                        fi
                        ;;
                esac
            fi
        fi

        # If this word isn't an '=', put it in 'previous' and go onto the
        # next word.
        if [ $word != "=" ]; then
            previous=$word
        fi
    done

    # If control reached here, then there weren't any tables on the
    # command-line until now.
    return 1;
}





# Find the requested file from the existing tokens on the command-line.
#
# INPUT ARGUMENTS:
#    1) Mode of file ('table', 'image', 'fits').
#    2) Name of option containing file names.
#
# WRITTEN VARIABLES (should be defined before this function).
#     given_file: file name of given table.
_gnuastro_autocomplete_given_file(){

    # Set inputs (for each readability).
    local mode="$1"
    local name="$2"
    local read_option_value=""

    # If 'name' is emtpy, we should look in the arguments, otherwise, we
    # should look into the options.
    if [ x"$name" = x ]; then
        if _gnuastro_autocomplete_first_in_arguments $mode; then
            # given_file is written by the function as a side-effect.
            local just_a_place_holder=1
        fi

    # We are looking for a certain option.
    else
        # Read the given option's value.
        _gnuastro_autocomplete_read_option_value "$name"

        # If we are in image-mode, and the found file has an image, then
        # put the name in 'given_file' (final output). Same for tables.
        case $mode in
            fits)
                if _gnuastro_autocomplete_is_fits "$read_option_value"; then
                    given_file="$read_option_value"
                fi
                ;;
            image)
                if _gnuastro_autocomplete_fits_has_image \
                       "$read_option_value"; then
                    given_file="$read_option_value"
                fi
                ;;
            table)
                if _gnuastro_autocomplete_is_table \
                       "$read_option_value"; then
                    given_file="$read_option_value"
                fi
                ;;
        esac
    fi

}





# Find the requested filename and HDU within the already entered
# command-line. This option needs three arguments:
#
# INPUT ARGUMENTS
#    1) Mode of file ('table' or 'image').
#    2) The filename option (if empty string, 1st argument).
#    3) The HDU option (only necessary if the file is FITS).
#
# WRITTEN VARIABLES (should be defined before this function).
#    given_file: file name of given table/image.
#    given_hdu:  HDU of given table/table.
_gnuastro_autocomplete_given_file_and_hdu(){

    # Set inputs (for each readability).
    local mode=$1
    local name=$2
    local hduoption=$3

    # Internal variables.
    local read_option_value=""

    # Initialize the outputs (defined before this).
    given_hdu=""
    given_file=""

    # First, confirm the table file name. If requested table is in an
    # argument, 'name' will be empty.
    _gnuastro_autocomplete_given_file $mode $name

    # If a file name existed and it is a FITS file, find the HDU given in
    # the option.
    if [ x"$given_file" != x ] \
           && _gnuastro_autocomplete_is_fits "$given_file"; then
        _gnuastro_autocomplete_read_option_value $hduoption
        given_hdu="$read_option_value"
    fi
}




















#######################################################################
############               Completion replies              ############
#######################################################################
# Given a set of strings, select the one that matches the first argument
_gnuastro_autocomplete_compreply_from_string(){

    # Internal variables (for easy reading).
    local string="$1"
    local match="$2"

    # When there isn't any match string, just add everything.
    if [ x"$match" = x ]; then
        for v in $string; do COMPREPLY+=("$v"); done

    # When there is a match, limit it. We aren't using 'grep' because it
    # can confuse a possible '--XXX', with its own options on some systems
    # (and placing a '--' before the search string may not be portable).
    else
        for v in $(echo $string \
                       | awk '{for(i=1;i<=NF;++i) \
                                 if($i ~ /^'$match'/) print $i}'); do
            COMPREPLY+=("$v");
        done
    fi
}




# Some options take multiple values, separated by a comma (for example
# '--option=A,B,C'). Once the possible solutions have been found by the
# caller function, this function will decide how to print the suggestions:
# add a comma or not.
_gnuastro_autocomplete_compreply_comma_when_matched(){

    # Input arguments:
    local replies="$1"
    local tomatch="$2"
    local fullmatch="$3"
    local continuematch="$4"

    # Add the completion replies.
    if [ x"$continuematch" = x ]; then
        for c in $replies; do COMPREPLY+=("$c"); done
    else
        # If there is only one match, include the previously specified
        # replies in the final filled value and append an ',' to let the
        # user specify more values to the option.
        if [ $(echo $replies | wc -w) = 1 ]; then

            # In the continue-mode we don't want the final value to be
            # appended with a space.
            compopt -o nospace

            # When 'fullmatch' and 'tomatch' are the same, this was the
            # first requested reply, so we can safely just print it with a
            # comma.
            if [ x"$fullmatch" = x"$tomatch" ]; then
                COMPREPLY+=("$replies,")

            # This was not the first reply, so we need to add the old ones
            # as a prefix. But we first need to remove any possible start
            # of the current reply.
            else
                local oldreps=$(echo "$fullmatch" | sed -e's|'$tomatch'$||')
                COMPREPLY+=("$oldreps$replies,")
            fi

        # There was more than one matching reply, so continue suggesting
        # with only the column names.
        else
            local oldreps=$(echo "$fullmatch" | sed -e's|'$tomatch'$||')
            for c in $replies; do COMPREPLY+=("$oldreps$c"); done
        fi
    fi
}





# Add completion replies for the values to '--searchin'.
_gnuastro_autocomplete_compreply_searchin(){
    _gnuastro_autocomplete_compreply_from_string \
        "name unit comment" "$1"
}





# Add completion replies for the values to '--searchin'.
_gnuastro_autocomplete_compreply_tableformat(){
    _gnuastro_autocomplete_compreply_from_string \
        "fits-ascii fits-binary txt" "$1"
}





# Add completion replies for the values to '--numthreads'.
_gnuastro_autocomplete_compreply_numthreads(){
    if nproc &> /dev/null; then
        local numthreads="$(seq $(nproc))"
        _gnuastro_autocomplete_compreply_from_string \
            "$numthreads" "$1"
    fi
}





# Values to the common '--interpmetric' option.
_gnuastro_autocomplete_compreply_interpmetric(){
    _gnuastro_autocomplete_compreply_from_string \
        "radial manhattan" "$1"
}





# Values to the common '--type' option.
_gnuastro_autocomplete_compreply_numbertype(){
    _gnuastro_autocomplete_compreply_from_string \
        "uint8 int8 uint16 int16 uint32 int32 uint64 int64 float32 float64" \
        "$1"
}





# Values to the common '--wcslinearmatrix' option.
_gnuastro_autocomplete_compreply_wcslinearmatrix(){
    _gnuastro_autocomplete_compreply_from_string \
        "cd pc" "$1"
}





# Add matching options to the completion replies.
_gnuastro_autocomplete_compreply_options_all(){

    # Variable only necessary here.
    local options_match=""

    # Get the list of option names (with an '=' after those that need a
    # value (if 'options_all' isn't already set)
    if [ x"$options_all" = x ]; then
        _gnuastro_autocomplete_option_list
    fi

    # Limit the options to those that start with the already given portion.
    if [ x$1 = x ]; then
        options_match="$options_all"
    else
        # We aren't using 'grep' because it can confuse the '--XXX' with
        # its own options on some systems (and placing a '--' before the
        # search string may not be portable).
        options_match=$(echo "$options_all" | awk '/^'$1'/')
    fi

    # Add the list of options.
    for f in $options_match; do COMPREPLY+=("$f"); done

    # Disable the extra space on the command-line after the match, only for
    # this run (only relevant when we have found the match).
    compopt -o nospace
}





# Add a file into the completion replies. The main point is to remove the
# fixed directory name prefix of the file (that is appended by 'ls').
#
# It takes two arguments:
#
#   1. Base string (that was fed into 'ls' to find the full string).
#      This string CAN BE EMPTY.
#   2. Full string.
#
# If the first is a full directory, then it will remove it from the full
# string before saving string (which is standard in autocomplete (the user
# has already given it and it is just annoying!).
_gnuastro_autocomplete_compreply_file(){

    # For some reason, when there are multiple matches in a sub-directory,
    # removing the directory removes the whole thing that the user has
    # already typed! So the part below (which was the main purpose of this
    # function is currently commented). Until that bug is fixed, we'll just
    # return the full file name. Here is how you can reproduce the problem
    # (with the MAIN FUNCTION below uncommented and the WORK AROUND
    # commented):
    #
    #    $ ls
    #    image.fits
    #    $ mkdir subdir
    #    $ mv image.fits subdir/ab-123.fits
    #    $ cp subdir/ab-123.fits subdir/ab-456.fits
    #    $ astcrop subdir/ab-[TAB]

    # MAIN FUNCTION
    #if [ x$1 != x ] && [ -d $1 ]; then COMPREPLY+=("${2#$1}")
    #else                               COMPREPLY+=("$2")
    #fi

    # WORK AROUND
    COMPREPLY+=("$2")
}





# Add all the HDUs that contain a table/image in the first argument (a FITS
# file) into the completion replies.
#
# INPUT ARGUMENTS
#    1) Mode of file ('table', 'image', or 'all').
#    2) Name of file.
#    3) Existing argument.
_gnuastro_autocomplete_compreply_hdus(){

    # Local variables (for easy reading)
    local mode="$1"
    local given_file="$2"
    local matchstr="$3"

    if _gnuastro_autocomplete_is_fits "$given_file"; then

        # Get list of the file's HDUs.
        hdus=$("$gnuastro_prefix"/astfits "$given_file" \
                                 --list"$mode"hdus)

        # Add the matching ones into COMPREPLY.
        _gnuastro_autocomplete_compreply_from_string \
            "$hdus" "$matchstr"
    fi
}





# Add all the keywords in current HDU to the replies.
#
# INPUT ARGUMENTS:
#    1) FITS file name.
#    2) HDU within the FITS file.
#    3) Existing argument (to match).
#    4) If a comma should be appended in case of match.
_gnuastro_autocomplete_compreply_keys(){

    # Input arguments.
    local given_file="$1"
    local given_hdu="$2"
    local fullmatch="$3"
    local continuematch="$4"

    # Local variables.
    local keys=""
    local tomatch=""

    # Keywords can sometimes be given in series (for example
    # '--keyvalue=A,B,C'). In this case, the caller will give a non-empty
    # value to the fourth argument ('continuematch'). We therefore need to
    # will take the last component of the comma-separated list for the
    # matching of the next element.
    if [ x"$continuematch" = x ]; then
        tomatch="$fullmatch"
    else
        tomatch=$(echo $fullmatch | awk 'BEGIN{FS=","} {print $NF}')
    fi

    # Get list of keywords.
    keys=$("$gnuastro_prefix"/astfits "$given_file" \
                             --hdu="$given_hdu" \
                             --printkeynames \
               | grep ^$tomatch)

    _gnuastro_autocomplete_compreply_comma_when_matched \
        "$keys" "$tomatch" "$fullmatch" "$continuematch"
}





# Fill the replies with certain files.
_gnuastro_autocomplete_compreply_directories(){

    # For easy reading.
    local arg=$1

    # Get the list of directories (all ending with '/', with '-d' we are
    # telling 'ls' to not go into sub-directories).
    local directs=($(ls -d "$arg"*/ 2> /dev/null))

    # If a directory exists at all.
    if [ x"${directs[0]}" != x ]; then

        # If there is only one match.
        if [ x"${directs[1]}" = x ]; then

            # If there are sub-directories, then keep the ending '/' and
            # don't add space (the user may want to add sub-directories).
            if $(ls -d "${directs[0]}"/*/ &> /dev/null); then
                COMPREPLY+=("$d")
                compopt -o nospace

            # There are no sub-directories. In this case, remove the ending
            # '/' and let Bash add a space after the matched name.
            else
                COMPREPLY+=($(echo "${directs[0]}" | sed -e's|/$||'))
            fi

        # More than one match: go over all the matches and add them. we
        # should avoid printing a space (so the sub-directories continue in
        # the same token). Also RECALL THAT '$d' ALREADY ENDS WITH A '/'
        # HERE.
        else
            for d in ${directs[*]}; do
                COMPREPLY+=("$d")
                compopt -o nospace
            done
        fi
    fi
}





# Fill the replies with all image formats (note that the 'image' of
# '_gnuastro_autocomplete_compreply_files_certain' is only FITS files)
_gnuastro_autocomplete_compreply_images_all(){

    # Local variables to be filled by functions.
    local arg="$1"
    local ls_in=""
    local files=""
    local suffixes_jpeg=""
    local suffixes_tiff=""

    # Get the FITS images.
    _gnuastro_autocomplete_compreply_files_certain image "$arg"

    # If the given name doesn't have a suffix, then search for desired
    # suffixes.
    if [ x"$arg" = x"$(echo $arg | cut -d. -f1)" ]; then

        # Since the other formats are checked by suffix and there are many
        # suffixes, its easier to just call 'ls' once.
        _gnuastro_autocomplete_compreply_suffixes_jpeg
        _gnuastro_autocomplete_compreply_suffixes_tiff
        for s in $suffixes_jpeg $suffixes_tiff; do
            ls_in="$ls_in "$arg"*$s"
        done

    # The given argument already contains a suffix. So you can safely
    # ignore the matched suffixes.
    else
        ls_in=$arg"*"
    fi

    # Find the matching files and add them to the replies.
    files=($(ls -d $ls_in 2> /dev/null))
    for f in ${files[*]}; do
        if [ -d "$f" ]; then
            COMPREPLY+=("$f/")
            compopt -o nospace
        else
            _gnuastro_autocomplete_compreply_file "$arg" "$f"
        fi
    done
}




# Fill the replies with certain files.
_gnuastro_autocomplete_compreply_files_certain(){

    # For easy reading.
    local arg=$2
    local mode=$1

    # Get list of matching files (with '-d' we are telling 'ls' to not go
    # into sub-directories).
    local files=($(ls -d "$arg"* 2> /dev/null))

    # Parse the list of files and add it when it is a directory or it can
    # be read as a table.
    for f in ${files[*]}; do
        if [ -d "$f" ]; then
            COMPREPLY+=("$f/")
            compopt -o nospace
        else
            case "$mode" in
                fits)
                    if _gnuastro_autocomplete_is_fits "$f"; then
                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
                    fi
                    ;;
                image)
                    if _gnuastro_autocomplete_fits_has_image "$f"; then
                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
                    fi
                    ;;
                table)
                    if _gnuastro_autocomplete_is_table "$f"; then
                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
                    fi
                    ;;
                source_c)
                    if $(echo $f | grep "\.c$" &> /dev/null); then
                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
                    fi
                    ;;
                source_la)
                    if $(echo $f | grep "\.la$" &> /dev/null); then
                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
                    fi
                    ;;
            esac
        fi
    done
}





# Add all table columns in given file (possibly with HDU) that start with
# the given string into the completion replies.
_gnuastro_autocomplete_compreply_table_columns(){

    # Inputs
    local table_file="$1"
    local table_hdu="$2"
    local fullmatch="$3"
    local continuematch="$4"

    # Internal
    local tomatch=""
    local columns=""
    local hdu_option=""

    # If no file has been given, don't set anything, just return.
    if [ x"$table_file" = x ]; then return 0; fi

    # If a HDU is given, then add it to the options.
    if [ x"$table_hdu" != x ]; then hdu_option="--hdu=$table_hdu"; fi

    # Columns can usually be given in series (for example
    # '--column=A,B,C'). In this case, the caller will give a non-empty
    # value to the fourth argument ('continuematch'). We therefore need to
    # will take the last component of the comma-separated list for the
    # matching of the next element.
    if [ x"$continuematch" = x ]; then
        tomatch="$fullmatch"
    else
        tomatch=$(echo $fullmatch | awk 'BEGIN{FS=","} {print $NF}')
    fi

    # Get the list of columns from the output of '--information': the
    # column names are the second column of the lines that start with a
    # number. If there is no column name, print the column number.
    #
    # We are forcing 'awk' to read after the second line of 'asttable'
    # output, because the second line contains the filename. The filename
    # might start with numbers. If so, there will be an unwanted '(hdu:'
    # printed in the results. Here, 'awk' will print the second column in
    # lines that start with a number.
    columns=$("$gnuastro_prefix"/asttable --information \
                                "$table_file" $hdu_option \
                  | awk 'NR>2 && /^[0-9]/ { \
                            if($2=="n/a") print $1; else print $2 \
                         }' \
                  | grep ^$tomatch)

    # Add the necessary columns in a comma-separated manner.
    _gnuastro_autocomplete_compreply_comma_when_matched \
        "$columns" "$tomatch" "$fullmatch" "$continuematch"
}
