    001    /**
    002     *      jline - Java console input library
    003     *      Copyright (c) 2002, 2003, 2004, 2005, Marc Prud'hommeaux <mwp1@cornell.edu>
    004     *      All rights reserved.
    005     *
    006     *      Redistribution and use in source and binary forms, with or
    007     *      without modification, are permitted provided that the following
    008     *      conditions are met:
    009     *
    010     *      Redistributions of source code must retain the above copyright
    011     *      notice, this list of conditions and the following disclaimer.
    012     *
    013     *      Redistributions in binary form must reproduce the above copyright
    014     *      notice, this list of conditions and the following disclaimer
    015     *      in the documentation and/or other materials provided with
    016     *      the distribution.
    017     *
    018     *      Neither the name of JLine nor the names of its contributors
    019     *      may be used to endorse or promote products derived from this
    020     *      software without specific prior written permission.
    021     *
    022     *      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    023     *      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
    024     *      BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
    025     *      AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
    026     *      EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
    027     *      FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
    028     *      OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    029     *      PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    030     *      DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
    031     *      AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    032     *      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    033     *      IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    034     *      OF THE POSSIBILITY OF SUCH DAMAGE.
    035     */
    036    package jline;
    037
    038    import java.io.*;
    039    import java.util.*;
    040
    041
    042    /**
    043     *  A file name completor takes the buffer and issues a list of
    044     *  potential completions.
    045     *
    046     *      <p>
    047     *      This completor tries to behave as similar as possible to
    048     *      <i>bash</i>'s file name completion (using GNU readline)
    049     *      with the following exceptions:
    050     *
    051     *      <ul>
    052     *      <li>Candidates that are directories will end with "/"</li>
    053     *      <li>Wildcard regular expressions are not evaluated or replaced</li>
    054     *      <li>The "~" character can be used to represent the user's home,
    055     *              but it cannot complete to other users' homes, since java does
    056     *              not provide any way of determining that easily</li>
    057     *      </ul>
    058     *
    059     *      <p>TODO</p>
    060     *      <ul>
    061     *      <li>Handle files with spaces in them</li>
    062     *      <li>Have an option for file type color highlighting</li>
    063     *      </ul>
    064     *
    065     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
    066     */
    067    public class FileNameCompletor
    068            implements Completor
    069    {
    070            public int complete (final String buf, final int cursor,
    071                    final List candidates)
    072            {
    073                    String buffer = buf == null ? "" : buf;
    074
    075                    String translated = buffer;
    076
    077                    // special character: ~ maps to the user's home directory
    078                    if (translated.startsWith ("~" + File.separator))
    079                    {
    080                            translated = System.getProperty ("user.home")
    081                                    + translated.substring (1);
    082                    }
    083                    else if (translated.startsWith ("~"))
    084                    {
    085                            translated = new File (System.getProperty ("user.home"))
    086                                    .getParentFile ().getAbsolutePath ();
    087                    }
    088                    else if (!(translated.startsWith (File.separator)))
    089                    {
    090                            translated = new File ("").getAbsolutePath ()
    091                                    + File.separator + translated;
    092                    }
    093
    094                    File f = new File (translated);
    095
    096                    final File dir;
    097
    098                    if (translated.endsWith (File.separator))
    099                            dir = f;
    100                    else
    101                            dir = f.getParentFile ();
    102
    103                    final File [] entries = dir == null ? new File [0] : dir.listFiles ();
    104
    105                    try
    106                    {
    107                            return matchFiles (buffer, translated, entries, candidates);
    108                    }
    109                    finally
    110                    {
    111                            // we want to output a sorted list of files
    112                            sortFileNames (candidates);
    113                    }
    114            }
    115
    116
    117            protected void sortFileNames (final List fileNames)
    118            {
    119                    Collections.sort (fileNames);
    120            }
    121
    122
    123            /**
    124             *  Match the specified <i>buffer</i> to the array of <i>entries</i>
    125             *  and enter the matches into the list of <i>candidates</i>. This method
    126             *  can be overridden in a subclass that wants to do more
    127             *  sophisticated file name completion.
    128             *
    129             *  @param      buffer          the untranslated buffer
    130             *  @param      translated      the buffer with common characters replaced
    131             *  @param      entries         the list of files to match
    132             *  @param      candidates      the list of candidates to populate
    133             *
    134             *  @return  the offset of the match
    135             */
    136            public int matchFiles (String buffer, String translated,
    137                    File [] entries, List candidates)
    138            {
    139                    if (entries == null)
    140                            return -1;
    141
    142                    int matches = 0;
    143
    144                    // first pass: just count the matches
    145                    for (int i = 0; i < entries.length; i++)
    146                    {
    147                            if (entries [i].getAbsolutePath ().startsWith (translated))
    148                            {
    149                                    matches++;
    150                            }
    151                    }
    152
    153                    // green - executable
    154                    // blue - directory
    155                    // red - compressed
    156                    // cyan - symlink
    157                    for (int i = 0; i < entries.length; i++)
    158                    {
    159                            if (entries [i].getAbsolutePath ().startsWith (translated))
    160                            {
    161                                    String name = entries [i].getName ()
    162                                            + (matches == 1 && entries [i].isDirectory ()
    163                                                    ? File.separator : " ");
    164
    165                                    /*
    166                                    if (entries [i].isDirectory ())
    167                                    {
    168                                            name = new ANSIBuffer ().blue (name).toString ();
    169                                    }
    170                                    */
    171
    172                                    candidates.add (name);
    173                            }
    174                    }
    175
    176
    177                    final int index = buffer.lastIndexOf (File.separator);
    178                    return index + File.separator.length ();
    179            }
    180    }
    181