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 */
007package jline;
008
009import java.io.*;
010import 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 */
037public 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}