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 }