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