001    /**
002     *      jline - Java console input library
003     *      Copyright (c) 2002-2006, 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