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 import java.text.MessageFormat;
041
042 /**
043 * <p>
044 * A {@link CompletionHandler} that deals with multiple distinct completions
045 * by outputting the complete list of possibilities to the console. This
046 * mimics the behavior of the
047 * <a href="http://www.gnu.org/directory/readline.html">readline</a>
048 * library.
049 * </p>
050 *
051 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
052 */
053 public class CandidateListCompletionHandler
054 implements CompletionHandler
055 {
056 private static ResourceBundle loc = ResourceBundle.getBundle (
057 CandidateListCompletionHandler.class.getName ());
058
059
060 public boolean complete (final ConsoleReader reader,
061 final List candidates, final int pos)
062 throws IOException
063 {
064 CursorBuffer buf = reader.getCursorBuffer ();
065
066 // if there is only one completion, then fill in the buffer
067 if (candidates.size () == 1)
068 {
069 String value = candidates.get (0).toString ();
070
071 // fail if the only candidate is the same as the current buffer
072 if (value.equals (buf.toString ()))
073 return false;
074 setBuffer (reader, value, pos);
075 return true;
076 }
077 else if (candidates.size () > 1)
078 {
079 String value = getUnambiguousCompletions (candidates);
080 String bufString = buf.toString ();
081 setBuffer (reader, value, pos);
082
083 // if we have changed the buffer, then just return withough
084 // printing out all the subsequent candidates
085 if (bufString.length () - pos + 1 != value.length ())
086 return true;
087 }
088
089 reader.printNewline ();
090 printCandidates (reader, candidates);
091
092 // redraw the current console buffer
093 reader.drawLine ();
094
095 return true;
096 }
097
098
099 private static void setBuffer (ConsoleReader reader,
100 String value, int offset)
101 throws IOException
102 {
103 while (reader.getCursorBuffer ().cursor >= offset
104 && reader.backspace ());
105 reader.putString (value);
106 reader.setCursorPosition (offset + value.length ());
107 }
108
109
110 /**
111 * Print out the candidates. If the size of the candidates
112 * is greated than the {@link getAutoprintThreshhold},
113 * they prompt with aq warning.
114 *
115 * @param candidates the list of candidates to print
116 */
117 private final void printCandidates (ConsoleReader reader,
118 Collection candidates)
119 throws IOException
120 {
121 Set distinct = new HashSet (candidates);
122
123 if (distinct.size () > reader.getAutoprintThreshhold ())
124 {
125 reader.printString (MessageFormat.format (
126 loc.getString ("display-candidates"),
127 new Object [] { new Integer (candidates.size ()) } ) + " ");
128
129 reader.flushConsole ();
130
131 int c;
132
133 String noOpt = loc.getString ("display-candidates-no");
134 String yesOpt = loc.getString ("display-candidates-yes");
135
136 while ((c = reader.readCharacter (
137 new char[] { yesOpt.charAt (0), noOpt.charAt (0) })) != -1)
138 {
139 if (noOpt.startsWith (new String (new char[] {(char)c})))
140 {
141 reader.printNewline ();
142 return;
143 }
144 else if (yesOpt.startsWith (new String (new char[] {(char)c})))
145 {
146 break;
147 }
148 else
149 {
150 reader.beep ();
151 }
152 }
153 }
154
155 // copy the values and make them distinct, without otherwise
156 // affecting the ordering. Only do it if the sizes differ.
157 if (distinct.size () != candidates.size ())
158 {
159 Collection copy = new ArrayList ();
160 for (Iterator i = candidates.iterator (); i.hasNext (); )
161 {
162 Object next = i.next ();
163 if (!(copy.contains (next)))
164 copy.add (next);
165 }
166
167 candidates = copy;
168 }
169
170 reader.printNewline ();
171 reader.printColumns (candidates);
172 }
173
174
175
176
177 /**
178 * Returns a root that matches all the {@link String} elements
179 * of the specified {@link List}, or null if there are
180 * no commalities. For example, if the list contains
181 * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
182 * method will return <i>foob</i>.
183 */
184 private final String getUnambiguousCompletions (final List candidates)
185 {
186 if (candidates == null || candidates.size () == 0)
187 return null;
188
189 // convert to an array for speed
190 String [] strings = (String [])candidates.toArray (
191 new String [candidates.size ()]);
192
193 String first = strings [0];
194 StringBuffer candidate = new StringBuffer ();
195 for (int i = 0; i < first.length (); i++)
196 {
197 if (startsWith (first.substring (0, i + 1), strings))
198 candidate.append (first.charAt (i));
199 else
200 break;
201 }
202
203 return candidate.toString ();
204 }
205
206
207 /**
208 * @return true is all the elements of <i>candidates</i>
209 * start with <i>starts</i>
210 */
211 private final boolean startsWith (final String starts,
212 final String [] candidates)
213 {
214 for (int i = 0; i < candidates.length; i++)
215 {
216 if (!candidates [i].startsWith (starts))
217 return false;
218 }
219
220 return true;
221 }
222 }
223