001    /*
002     * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
003     *
004     * This software is distributable under the BSD license. See the terms of the
005     * BSD license in the documentation provided with this software.
006     */
007    package jline;
008    
009    import java.io.*;
010    import java.util.*;
011    
012    /**
013     *  A file name completor takes the buffer and issues a list of
014     *  potential completions.
015     *
016     *  <p>
017     *  This completor tries to behave as similar as possible to
018     *  <i>bash</i>'s file name completion (using GNU readline)
019     *  with the following exceptions:
020     *
021     *  <ul>
022     *  <li>Candidates that are directories will end with "/"</li>
023     *  <li>Wildcard regular expressions are not evaluated or replaced</li>
024     *  <li>The "~" character can be used to represent the user's home,
025     *  but it cannot complete to other users' homes, since java does
026     *  not provide any way of determining that easily</li>
027     *  </ul>
028     *
029     *  <p>TODO</p>
030     *  <ul>
031     *  <li>Handle files with spaces in them</li>
032     *  <li>Have an option for file type color highlighting</li>
033     *  </ul>
034     *
035     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
036     */
037    public class FileNameCompletor implements Completor {
038        public int complete(final String buf, final int cursor,
039                            final List candidates) {
040            String buffer = (buf == null) ? "" : buf;
041    
042            String translated = buffer;
043    
044            // special character: ~ maps to the user's home directory
045            if (translated.startsWith("~" + File.separator)) {
046                translated = System.getProperty("user.home")
047                             + translated.substring(1);
048            } else if (translated.startsWith("~")) {
049                translated = new File(System.getProperty("user.home")).getParentFile()
050                                                                      .getAbsolutePath();
051            } else if (!(translated.startsWith(File.separator))) {
052                translated = new File("").getAbsolutePath() + File.separator
053                             + translated;
054            }
055    
056            File f = new File(translated);
057    
058            final File dir;
059    
060            if (translated.endsWith(File.separator)) {
061                dir = f;
062            } else {
063                dir = f.getParentFile();
064            }
065    
066            final File[] entries = (dir == null) ? new File[0] : dir.listFiles();
067    
068            try {
069                return matchFiles(buffer, translated, entries, candidates);
070            } finally {
071                // we want to output a sorted list of files
072                sortFileNames(candidates);
073            }
074        }
075    
076        protected void sortFileNames(final List fileNames) {
077            Collections.sort(fileNames);
078        }
079    
080        /**
081         *  Match the specified <i>buffer</i> to the array of <i>entries</i>
082         *  and enter the matches into the list of <i>candidates</i>. This method
083         *  can be overridden in a subclass that wants to do more
084         *  sophisticated file name completion.
085         *
086         *  @param        buffer                the untranslated buffer
087         *  @param        translated        the buffer with common characters replaced
088         *  @param        entries                the list of files to match
089         *  @param        candidates        the list of candidates to populate
090         *
091         *  @return  the offset of the match
092         */
093        public int matchFiles(String buffer, String translated, File[] entries,
094                              List candidates) {
095            if (entries == null) {
096                return -1;
097            }
098    
099            int matches = 0;
100    
101            // first pass: just count the matches
102            for (int i = 0; i < entries.length; i++) {
103                if (entries[i].getAbsolutePath().startsWith(translated)) {
104                    matches++;
105                }
106            }
107    
108            // green - executable
109            // blue - directory
110            // red - compressed
111            // cyan - symlink
112            for (int i = 0; i < entries.length; i++) {
113                if (entries[i].getAbsolutePath().startsWith(translated)) {
114                    String name =
115                        entries[i].getName()
116                        + (((matches == 1) && entries[i].isDirectory())
117                           ? File.separator : " ");
118    
119                    /*
120                    if (entries [i].isDirectory ())
121                    {
122                            name = new ANSIBuffer ().blue (name).toString ();
123                    }
124                    */
125                    candidates.add(name);
126                }
127            }
128    
129            final int index = buffer.lastIndexOf(File.separator);
130    
131            return index + File.separator.length();
132        }
133    }