001 /**
002 * jline - Java console input library
003 * Copyright (c) 2002,2003 Marc Prud'hommeaux mwp1@cornell.edu
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either
008 * version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public
016 * License along with this library; if not, write to the Free Software
017 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018 */
019 package jline;
020
021 import java.io.*;
022 import java.util.*;
023 import java.text.MessageFormat;
024
025 /**
026 * <p>
027 * A {@link CompletionHandler} that deals with multiple distinct completions
028 * by outputting the complete list of possibilities to the console. This
029 * mimics the behavior of the
030 * <a href="http://www.gnu.org/directory/readline.html">readline</a>
031 * library.
032 * </p>
033 *
034 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
035 */
036 public class CandidateListCompletionHandler
037 implements CompletionHandler
038 {
039 private static ResourceBundle loc = ResourceBundle.getBundle (
040 CandidateListCompletionHandler.class.getName ());
041
042
043 public boolean complete (final ConsoleReader reader,
044 final List candidates, final int pos)
045 throws IOException
046 {
047 CursorBuffer buf = reader.getCursorBuffer ();
048
049 // if there is only one completion, then fill in the buffer
050 if (candidates.size () == 1)
051 {
052 String value = candidates.get (0).toString ();
053
054 // fail if the only candidate is the same as the current buffer
055 if (value.equals (buf.toString ()))
056 return false;
057 setBuffer (reader, value, pos);
058 return true;
059 }
060 else if (candidates.size () > 1)
061 {
062 String value = getUnambiguousCompletions (candidates);
063 String bufString = buf.toString ();
064 setBuffer (reader, value, pos);
065
066 // if we have changed the buffer, then just return withough
067 // printing out all the subsequent candidates
068 if (bufString.length () - pos + 1 != value.length ())
069 return true;
070 }
071
072 reader.printNewline ();
073 printCandidates (reader, candidates);
074
075 // redraw the current console buffer
076 reader.drawLine ();
077
078 return true;
079 }
080
081
082 private static void setBuffer (ConsoleReader reader,
083 String value, int offset)
084 throws IOException
085 {
086 while (reader.getCursorBuffer ().cursor >= offset
087 && reader.backspace ());
088 reader.putString (value);
089 reader.setCursorPosition (offset + value.length ());
090 }
091
092
093 /**
094 * Print out the candidates. If the size of the candidates
095 * is greated than the {@link getAutoprintThreshhold},
096 * they prompt with aq warning.
097 *
098 * @param candidates the list of candidates to print
099 */
100 private final void printCandidates (ConsoleReader reader,
101 Collection candidates)
102 throws IOException
103 {
104 Set distinct = new HashSet (candidates);
105
106 if (distinct.size () > reader.getAutoprintThreshhold ())
107 {
108 reader.printString (MessageFormat.format (
109 loc.getString ("display-candidates"),
110 new Object [] { new Integer (candidates.size ()) } ) + " ");
111
112 reader.flushConsole ();
113
114 int c;
115
116 String noOpt = loc.getString ("display-candidates-no");
117 String yesOpt = loc.getString ("display-candidates-yes");
118
119 while ((c = reader.readCharacter (
120 new char[] { yesOpt.charAt (0), noOpt.charAt (0) })) != -1)
121 {
122 if (noOpt.startsWith (new String (new char[] {(char)c})))
123 {
124 reader.printNewline ();
125 return;
126 }
127 else if (yesOpt.startsWith (new String (new char[] {(char)c})))
128 {
129 break;
130 }
131 else
132 {
133 reader.beep ();
134 }
135 }
136 }
137
138 // copy the values and make them distinct, without otherwise
139 // affecting the ordering. Only do it if the sizes differ.
140 if (distinct.size () != candidates.size ())
141 {
142 Collection copy = new ArrayList ();
143 for (Iterator i = candidates.iterator (); i.hasNext (); )
144 {
145 Object next = i.next ();
146 if (!(copy.contains (next)))
147 copy.add (next);
148 }
149
150 candidates = copy;
151 }
152
153 reader.printNewline ();
154 reader.printColumns (candidates);
155 }
156
157
158
159
160 /**
161 * Returns a root that matches all the {@link String} elements
162 * of the specified {@link List}, or null if there are
163 * no commalities. For example, if the list contains
164 * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
165 * method will return <i>foob</i>.
166 */
167 private final String getUnambiguousCompletions (final List candidates)
168 {
169 if (candidates == null || candidates.size () == 0)
170 return null;
171
172 // convert to an array for speed
173 String [] strings = (String [])candidates.toArray (
174 new String [candidates.size ()]);
175
176 String first = strings [0];
177 StringBuffer candidate = new StringBuffer ();
178 for (int i = 0; i < first.length (); i++)
179 {
180 if (startsWith (first.substring (0, i + 1), strings))
181 candidate.append (first.charAt (i));
182 else
183 break;
184 }
185
186 return candidate.toString ();
187 }
188
189
190 /**
191 * @return true is all the elements of <i>candidates</i>
192 * start with <i>starts</i>
193 */
194 private final boolean startsWith (final String starts,
195 final String [] candidates)
196 {
197 for (int i = 0; i < candidates.length; i++)
198 {
199 if (!candidates [i].startsWith (starts))
200 return false;
201 }
202
203 return true;
204 }
205 }
206